hedgequantx 1.2.146 → 1.3.0

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": "1.2.146",
3
+ "version": "1.3.0",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -6,22 +6,18 @@
6
6
  const chalk = require('chalk');
7
7
  const inquirer = require('inquirer');
8
8
  const ora = require('ora');
9
- const figlet = require('figlet');
10
- const { execSync, spawn } = require('child_process');
11
- const path = require('path');
12
9
 
13
- const { ProjectXService, connections } = require('./services');
14
- const { RithmicService } = require('./services/rithmic');
15
- const { TradovateService } = require('./services/tradovate');
16
- const { PROPFIRM_CHOICES, getPropFirmsByPlatform, getPropFirm } = require('./config');
17
- const { getDevice, getSeparator, printLogo, getLogoWidth, drawBoxHeader, drawBoxFooter, centerText, createBoxMenu } = require('./ui');
18
- const { validateUsername, validatePassword, maskSensitive } = require('./security');
10
+ const { connections } = require('./services');
11
+ const { getLogoWidth, centerText, prepareStdin } = require('./ui');
19
12
 
20
13
  // Pages
21
14
  const { showStats } = require('./pages/stats');
22
15
  const { showAccounts } = require('./pages/accounts');
23
16
  const { algoTradingMenu } = require('./pages/algo');
24
17
 
18
+ // Menus
19
+ const { projectXMenu, rithmicMenu, tradovateMenu, addPropAccountMenu, dashboardMenu, handleUpdate } = require('./menus');
20
+
25
21
  // Current service reference
26
22
  let currentService = null;
27
23
  let currentPlatform = null; // 'projectx' or 'rithmic'
@@ -46,27 +42,6 @@ const restoreTerminal = () => {
46
42
  }
47
43
  };
48
44
 
49
- /**
50
- * Ensure stdin is ready for inquirer prompts
51
- * This fixes input leaking to bash after session restore
52
- */
53
- const prepareStdin = () => {
54
- try {
55
- // Remove any raw mode that might be left from previous operations
56
- if (process.stdin.isTTY && process.stdin.isRaw) {
57
- process.stdin.setRawMode(false);
58
- }
59
- // Remove any lingering keypress listeners
60
- process.stdin.removeAllListeners('keypress');
61
- process.stdin.removeAllListeners('data');
62
- // Pause stdin so inquirer can take control
63
- process.stdin.pause();
64
- // Small delay to let event loop settle
65
- } catch (e) {
66
- // Ignore errors
67
- }
68
- };
69
-
70
45
  // Register global handlers to restore terminal on exit/crash
71
46
  process.on('exit', restoreTerminal);
72
47
  process.on('SIGINT', () => { restoreTerminal(); process.exit(0); });
@@ -87,7 +62,10 @@ process.on('unhandledRejection', (reason) => {
87
62
  */
88
63
  const banner = async () => {
89
64
  console.clear();
90
- const boxWidth = getLogoWidth();
65
+ const termWidth = process.stdout.columns || 100;
66
+ const isMobile = termWidth < 60;
67
+ // Logo HEDGEQUANTX + X = 94 chars, need 98 for box (94 + 2 borders + 2 padding)
68
+ const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
91
69
  const innerWidth = boxWidth - 2;
92
70
  const version = require('../package.json').version;
93
71
 
@@ -123,8 +101,6 @@ const banner = async () => {
123
101
  }
124
102
 
125
103
  // Draw logo - compact for mobile, full for desktop
126
- const termWidth = process.stdout.columns || 80;
127
- const isMobile = termWidth < 60;
128
104
 
129
105
  console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
130
106
 
@@ -222,391 +198,6 @@ const banner = async () => {
222
198
  console.log();
223
199
  };
224
200
 
225
- /**
226
- * Login prompt with validation
227
- * @param {string} propfirmName - PropFirm display name
228
- * @returns {Promise<{username: string, password: string}>}
229
- */
230
- const loginPrompt = async (propfirmName) => {
231
- const device = getDevice();
232
- console.log();
233
- console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
234
- console.log();
235
-
236
- const credentials = await inquirer.prompt([
237
- {
238
- type: 'input',
239
- name: 'username',
240
- message: chalk.white.bold('Username:'),
241
- validate: (input) => {
242
- try {
243
- validateUsername(input);
244
- return true;
245
- } catch (e) {
246
- return e.message;
247
- }
248
- }
249
- },
250
- {
251
- type: 'password',
252
- name: 'password',
253
- message: chalk.white.bold('Password:'),
254
- mask: '*',
255
- validate: (input) => {
256
- try {
257
- validatePassword(input);
258
- return true;
259
- } catch (e) {
260
- return e.message;
261
- }
262
- }
263
- }
264
- ]);
265
-
266
- return credentials;
267
- };
268
-
269
- /**
270
- * ProjectX platform connection menu
271
- */
272
- const projectXMenu = async () => {
273
- const propfirms = getPropFirmsByPlatform('ProjectX');
274
- const boxWidth = getLogoWidth();
275
- const innerWidth = boxWidth - 2;
276
- const numCols = 3;
277
- const colWidth = Math.floor(innerWidth / numCols);
278
-
279
- // Build numbered list
280
- const numbered = propfirms.map((pf, i) => ({
281
- num: i + 1,
282
- key: pf.key,
283
- name: pf.displayName
284
- }));
285
-
286
- // PropFirm selection box
287
- console.log();
288
- console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
289
- console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
290
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
291
-
292
- // Display in 3 columns with fixed width alignment
293
- const rows = Math.ceil(numbered.length / numCols);
294
- const maxNum = numbered.length;
295
- const numWidth = maxNum >= 10 ? 4 : 3; // [XX] or [X]
296
-
297
- for (let row = 0; row < rows; row++) {
298
- let line = '';
299
- for (let col = 0; col < numCols; col++) {
300
- const idx = row + col * rows;
301
- if (idx < numbered.length) {
302
- const item = numbered[idx];
303
- const numStr = item.num.toString().padStart(2, ' ');
304
- const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
305
- const textLen = 4 + 1 + item.name.length; // [XX] + space + name
306
- const padding = colWidth - textLen - 2;
307
- line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
308
- } else {
309
- line += ' '.repeat(colWidth);
310
- }
311
- }
312
- // Adjust for exact width
313
- const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
314
- const adjust = innerWidth - lineLen;
315
- console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
316
- }
317
-
318
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
319
- const backText = ' ' + chalk.red('[X] Back');
320
- const backLen = '[X] Back'.length + 2;
321
- console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
322
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
323
- console.log();
324
-
325
- const validInputs = numbered.map(n => n.num.toString());
326
- validInputs.push('x', 'X');
327
-
328
- const { action } = await inquirer.prompt([
329
- {
330
- type: 'input',
331
- name: 'action',
332
- message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
333
- validate: (input) => {
334
- if (validInputs.includes(input)) return true;
335
- return `Please enter 1-${numbered.length} or X`;
336
- }
337
- }
338
- ]);
339
-
340
- if (action.toLowerCase() === 'x') return null;
341
-
342
- const selectedIdx = parseInt(action) - 1;
343
- const selectedPropfirm = numbered[selectedIdx];
344
-
345
- const credentials = await loginPrompt(selectedPropfirm.name);
346
- const spinner = ora('Authenticating...').start();
347
-
348
- try {
349
- const service = new ProjectXService(selectedPropfirm.key);
350
- const result = await service.login(credentials.username, credentials.password);
351
-
352
- if (result.success) {
353
- await service.getUser();
354
- connections.add('projectx', service, service.propfirm.name);
355
- currentService = service;
356
- currentPlatform = 'projectx';
357
- spinner.succeed(`Connected to ${service.propfirm.name}`);
358
- return service;
359
- } else {
360
- spinner.fail(result.error || 'Authentication failed');
361
- return null;
362
- }
363
- } catch (error) {
364
- spinner.fail(error.message);
365
- return null;
366
- }
367
- };
368
-
369
- /**
370
- * Rithmic platform connection menu
371
- */
372
- const rithmicMenu = async () => {
373
- const propfirms = getPropFirmsByPlatform('Rithmic');
374
- const boxWidth = getLogoWidth();
375
- const innerWidth = boxWidth - 2;
376
- const numCols = 3;
377
- const colWidth = Math.floor(innerWidth / numCols);
378
-
379
- // Build numbered list
380
- const numbered = propfirms.map((pf, i) => ({
381
- num: i + 1,
382
- key: pf.key,
383
- name: pf.displayName,
384
- systemName: pf.rithmicSystem
385
- }));
386
-
387
- // PropFirm selection box
388
- console.log();
389
- console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
390
- console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (RITHMIC)', innerWidth)) + chalk.cyan('║'));
391
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
392
-
393
- // Display in 3 columns with fixed width alignment
394
- const rows = Math.ceil(numbered.length / numCols);
395
-
396
- for (let row = 0; row < rows; row++) {
397
- let line = '';
398
- for (let col = 0; col < numCols; col++) {
399
- const idx = row + col * rows;
400
- if (idx < numbered.length) {
401
- const item = numbered[idx];
402
- const numStr = item.num.toString().padStart(2, ' ');
403
- const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
404
- const textLen = 4 + 1 + item.name.length;
405
- const padding = colWidth - textLen - 2;
406
- line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
407
- } else {
408
- line += ' '.repeat(colWidth);
409
- }
410
- }
411
- const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
412
- const adjust = innerWidth - lineLen;
413
- console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
414
- }
415
-
416
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
417
- const backText = ' ' + chalk.red('[X] Back');
418
- const backLen = '[X] Back'.length + 2;
419
- console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
420
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
421
- console.log();
422
-
423
- const validInputs = numbered.map(n => n.num.toString());
424
- validInputs.push('x', 'X');
425
-
426
- const { action } = await inquirer.prompt([
427
- {
428
- type: 'input',
429
- name: 'action',
430
- message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
431
- validate: (input) => {
432
- if (validInputs.includes(input)) return true;
433
- return `Please enter 1-${numbered.length} or X`;
434
- }
435
- }
436
- ]);
437
-
438
- if (action.toLowerCase() === 'x') return null;
439
-
440
- const selectedIdx = parseInt(action) - 1;
441
- const selectedPropfirm = numbered[selectedIdx];
442
-
443
- const credentials = await loginPrompt(selectedPropfirm.name);
444
- const spinner = ora('Connecting to Rithmic...').start();
445
-
446
- try {
447
- const service = new RithmicService(selectedPropfirm.key);
448
- const result = await service.login(credentials.username, credentials.password);
449
-
450
- if (result.success) {
451
- spinner.text = 'Fetching accounts...';
452
- const accResult = await service.getTradingAccounts();
453
-
454
- connections.add('rithmic', service, service.propfirm.name);
455
- currentService = service;
456
- currentPlatform = 'rithmic';
457
- spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
458
-
459
- // Small pause to see the success message
460
- await new Promise(r => setTimeout(r, 1500));
461
- return service;
462
- } else {
463
- spinner.fail(result.error || 'Authentication failed');
464
- await new Promise(r => setTimeout(r, 2000));
465
- return null;
466
- }
467
- } catch (error) {
468
- spinner.fail(`Connection error: ${error.message}`);
469
- await new Promise(r => setTimeout(r, 2000));
470
- return null;
471
- }
472
- };
473
-
474
- /**
475
- * Tradovate platform connection menu
476
- */
477
- const tradovateMenu = async () => {
478
- const propfirms = getPropFirmsByPlatform('Tradovate');
479
- const boxWidth = getLogoWidth();
480
- const innerWidth = boxWidth - 2;
481
-
482
- // Build numbered list
483
- const numbered = propfirms.map((pf, i) => ({
484
- num: i + 1,
485
- key: pf.key,
486
- name: pf.displayName
487
- }));
488
-
489
- // PropFirm selection box
490
- console.log();
491
- console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
492
- console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (TRADOVATE)', innerWidth)) + chalk.cyan('║'));
493
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
494
-
495
- // Display propfirms
496
- for (const item of numbered) {
497
- const numStr = item.num.toString().padStart(2, ' ');
498
- const text = ' ' + chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
499
- const textLen = 4 + 1 + item.name.length + 2;
500
- console.log(chalk.cyan('║') + text + ' '.repeat(innerWidth - textLen) + chalk.cyan('║'));
501
- }
502
-
503
- console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
504
- const backText = ' ' + chalk.red('[X] Back');
505
- const backLen = '[X] Back'.length + 2;
506
- console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
507
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
508
- console.log();
509
-
510
- const validInputs = numbered.map(n => n.num.toString());
511
- validInputs.push('x', 'X');
512
-
513
- const { action } = await inquirer.prompt([
514
- {
515
- type: 'input',
516
- name: 'action',
517
- message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
518
- validate: (input) => {
519
- if (validInputs.includes(input)) return true;
520
- return `Please enter 1-${numbered.length} or X`;
521
- }
522
- }
523
- ]);
524
-
525
- if (action.toLowerCase() === 'x') return null;
526
-
527
- const selectedIdx = parseInt(action) - 1;
528
- const selectedPropfirm = numbered[selectedIdx];
529
-
530
- const credentials = await loginPrompt(selectedPropfirm.name);
531
- const spinner = ora('Connecting to Tradovate...').start();
532
-
533
- try {
534
- const service = new TradovateService(selectedPropfirm.key);
535
- const result = await service.login(credentials.username, credentials.password);
536
-
537
- if (result.success) {
538
- spinner.text = 'Fetching accounts...';
539
- await service.getTradingAccounts();
540
-
541
- connections.add('tradovate', service, service.propfirm.name);
542
- currentService = service;
543
- currentPlatform = 'tradovate';
544
- spinner.succeed(`Connected to ${service.propfirm.name}`);
545
- return service;
546
- } else {
547
- spinner.fail(result.error || 'Authentication failed');
548
- return null;
549
- }
550
- } catch (error) {
551
- spinner.fail(error.message);
552
- return null;
553
- }
554
- };
555
-
556
- /**
557
- * Add Prop Account menu (select platform)
558
- */
559
- const addPropAccountMenu = async () => {
560
- const boxWidth = getLogoWidth();
561
- const innerWidth = boxWidth - 2;
562
- const col1Width = Math.floor(innerWidth / 2);
563
- const col2Width = innerWidth - col1Width;
564
-
565
- console.log();
566
- console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
567
- console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT', innerWidth)) + chalk.cyan('║'));
568
- console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
569
-
570
- const menuRow = (left, right) => {
571
- const leftText = ' ' + left;
572
- const rightText = right ? ' ' + right : '';
573
- const leftLen = leftText.replace(/\x1b\[[0-9;]*m/g, '').length;
574
- const rightLen = rightText.replace(/\x1b\[[0-9;]*m/g, '').length;
575
- const leftPad = col1Width - leftLen;
576
- const rightPad = col2Width - rightLen;
577
- console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
578
- };
579
-
580
- menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
581
- menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Back'));
582
-
583
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
584
- console.log();
585
-
586
- const { action } = await inquirer.prompt([
587
- {
588
- type: 'input',
589
- name: 'action',
590
- message: chalk.cyan('Enter choice (1/2/3/X):'),
591
- validate: (input) => {
592
- const valid = ['1', '2', '3', 'x', 'X'];
593
- if (valid.includes(input)) return true;
594
- return 'Please enter 1, 2, 3 or X';
595
- }
596
- }
597
- ]);
598
-
599
- const actionMap = {
600
- '1': 'projectx',
601
- '2': 'rithmic',
602
- '3': 'tradovate',
603
- 'x': null,
604
- 'X': null
605
- };
606
-
607
- return actionMap[action];
608
- };
609
-
610
201
  /**
611
202
  * Main connection menu
612
203
  */
@@ -663,205 +254,6 @@ const mainMenu = async () => {
663
254
  return actionMap[action] || 'exit';
664
255
  };
665
256
 
666
- /**
667
- * Dashboard menu after login
668
- * @param {Object} service - Connected service
669
- */
670
- const dashboardMenu = async (service) => {
671
- const user = service.user;
672
- const boxWidth = getLogoWidth();
673
- const W = boxWidth - 2; // Same width as logo (inner width)
674
-
675
- // Helper to center text
676
- const centerLine = (text, width) => {
677
- const pad = Math.floor((width - text.length) / 2);
678
- return ' '.repeat(Math.max(0, pad)) + text + ' '.repeat(Math.max(0, width - pad - text.length));
679
- };
680
-
681
- // Helper to pad text left
682
- const padLine = (text, width) => {
683
- return ' ' + text + ' '.repeat(Math.max(0, width - text.length - 1));
684
- };
685
-
686
- // Dashboard box header
687
- console.log();
688
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
689
- console.log(chalk.cyan('║') + chalk.yellow.bold(centerLine('Welcome, HQX Trader!', W)) + chalk.cyan('║'));
690
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
691
-
692
- // Connection info - show all active connections in boxes (max 3 per row)
693
- const allConns = connections.getAll();
694
- if (allConns.length > 0) {
695
- const maxPerRow = 3;
696
- const boxPadding = 2; // padding inside each mini-box
697
- const gap = 2; // gap between boxes
698
-
699
- // Calculate box width based on number of connections (max 3)
700
- const numBoxes = Math.min(allConns.length, maxPerRow);
701
- const totalGaps = (numBoxes - 1) * gap;
702
- const boxWidth = Math.floor((W - totalGaps - 2) / numBoxes); // -2 for outer padding
703
-
704
- // Process connections in rows of 3
705
- for (let rowStart = 0; rowStart < allConns.length; rowStart += maxPerRow) {
706
- const rowConns = allConns.slice(rowStart, rowStart + maxPerRow);
707
- const numInRow = rowConns.length;
708
- const rowBoxWidth = Math.floor((W - (numInRow - 1) * gap - 2) / numInRow);
709
-
710
- // Top border of boxes
711
- let topLine = ' ';
712
- for (let i = 0; i < numInRow; i++) {
713
- topLine += '┌' + '─'.repeat(rowBoxWidth - 2) + '┐';
714
- if (i < numInRow - 1) topLine += ' '.repeat(gap);
715
- }
716
- const topPad = W - topLine.length;
717
- console.log(chalk.cyan('║') + chalk.green(topLine) + ' '.repeat(Math.max(0, topPad)) + chalk.cyan('║'));
718
-
719
- // Content of boxes
720
- let contentLine = ' ';
721
- for (let i = 0; i < numInRow; i++) {
722
- const connText = rowConns[i].propfirm || rowConns[i].type || 'Connected';
723
- const truncated = connText.length > rowBoxWidth - 4 ? connText.slice(0, rowBoxWidth - 7) + '...' : connText;
724
- const innerWidth = rowBoxWidth - 4; // -2 for borders, -2 for padding
725
- const textPad = Math.floor((innerWidth - truncated.length) / 2);
726
- const textPadRight = innerWidth - truncated.length - textPad;
727
- contentLine += '│ ' + ' '.repeat(textPad) + truncated + ' '.repeat(textPadRight) + ' │';
728
- if (i < numInRow - 1) contentLine += ' '.repeat(gap);
729
- }
730
- const contentPad = W - contentLine.length;
731
- console.log(chalk.cyan('║') + chalk.green(contentLine) + ' '.repeat(Math.max(0, contentPad)) + chalk.cyan('║'));
732
-
733
- // Bottom border of boxes
734
- let bottomLine = ' ';
735
- for (let i = 0; i < numInRow; i++) {
736
- bottomLine += '└' + '─'.repeat(rowBoxWidth - 2) + '┘';
737
- if (i < numInRow - 1) bottomLine += ' '.repeat(gap);
738
- }
739
- const bottomPad = W - bottomLine.length;
740
- console.log(chalk.cyan('║') + chalk.green(bottomLine) + ' '.repeat(Math.max(0, bottomPad)) + chalk.cyan('║'));
741
- }
742
- }
743
-
744
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
745
-
746
- // Menu options in 2 columns
747
- const col1Width = Math.floor(W / 2);
748
- const col2Width = W - col1Width;
749
-
750
- const menuRow = (left, right) => {
751
- const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
752
- const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
753
- const leftPad = ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
754
- const rightPad = ' '.repeat(Math.max(0, col2Width - rightPlain.length - 2));
755
- console.log(chalk.cyan('║') + ' ' + left + leftPad + ' ' + (right || '') + rightPad + chalk.cyan('║'));
756
- };
757
-
758
- menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
759
- menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.cyan('[A] Algo-Trading'));
760
- menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
761
-
762
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
763
- console.log();
764
-
765
- const { action } = await inquirer.prompt([
766
- {
767
- type: 'input',
768
- name: 'action',
769
- message: chalk.cyan('Enter choice (1/2/+/A/U/X):'),
770
- validate: (input) => {
771
- const valid = ['1', '2', '+', 'a', 'A', 'u', 'U', 'x', 'X'];
772
- if (valid.includes(input)) return true;
773
- return 'Please enter a valid option';
774
- }
775
- }
776
- ]);
777
-
778
- // Map input to action
779
- const actionMap = {
780
- '1': 'accounts',
781
- '2': 'stats',
782
- '+': 'add_prop_account',
783
- 'a': 'algotrading',
784
- 'A': 'algotrading',
785
- 'u': 'update',
786
- 'U': 'update',
787
- 'x': 'disconnect',
788
- 'X': 'disconnect'
789
- };
790
-
791
- return actionMap[action] || 'accounts';
792
- };
793
-
794
- /**
795
- * Handles the update process with auto-restart
796
- */
797
- const handleUpdate = async () => {
798
- const { execSync: exec } = require('child_process');
799
- const pkg = require('../package.json');
800
- const currentVersion = pkg.version;
801
- const spinner = ora('Checking for updates...').start();
802
-
803
- try {
804
- // Check latest version on npm
805
- spinner.text = 'Checking npm registry...';
806
- let latestVersion;
807
- try {
808
- latestVersion = exec('npm view hedgequantx version', { stdio: 'pipe' }).toString().trim();
809
- } catch (e) {
810
- spinner.fail('Cannot reach npm registry');
811
- console.log();
812
- await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
813
- return;
814
- }
815
-
816
- if (currentVersion === latestVersion) {
817
- spinner.succeed('Already up to date!');
818
- console.log();
819
- console.log(chalk.green(` ✓ You have the latest version of HedgeQuantX CLI: v${currentVersion}`));
820
- console.log();
821
- await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
822
- return;
823
- }
824
-
825
- // Update via npm
826
- spinner.text = `Updating v${currentVersion} -> v${latestVersion}...`;
827
- try {
828
- exec('npm install -g hedgequantx@latest', { stdio: 'pipe' });
829
- } catch (e) {
830
- spinner.fail('Update failed - try manually: npm install -g hedgequantx@latest');
831
- console.log(chalk.gray(` Error: ${e.message}`));
832
- console.log();
833
- await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
834
- return;
835
- }
836
-
837
- spinner.succeed('CLI updated!');
838
- console.log();
839
- console.log(chalk.green(` ✓ Updated: v${currentVersion} -> v${latestVersion}`));
840
- console.log();
841
- console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
842
- console.log();
843
-
844
- // Small delay so user can see the message
845
- await new Promise(resolve => setTimeout(resolve, 1500));
846
-
847
- // Restart the CLI automatically
848
- const { spawn } = require('child_process');
849
- const child = spawn('hedgequantx', [], {
850
- stdio: 'inherit',
851
- detached: true,
852
- shell: true
853
- });
854
- child.unref();
855
- process.exit(0);
856
-
857
- } catch (error) {
858
- spinner.fail('Update failed: ' + error.message);
859
- console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
860
- console.log();
861
- await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
862
- }
863
- };
864
-
865
257
  /**
866
258
  * Main application loop
867
259
  */
@@ -965,4 +357,4 @@ const run = async () => {
965
357
  }
966
358
  };
967
359
 
968
- module.exports = { run, banner, loginPrompt, mainMenu, dashboardMenu };
360
+ module.exports = { run, banner, mainMenu, dashboardMenu };