hedgequantx 1.7.7 → 1.7.9

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.7.7",
3
+ "version": "1.7.9",
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": {
@@ -41,11 +41,11 @@
41
41
  "src/"
42
42
  ],
43
43
  "dependencies": {
44
- "@clack/prompts": "^0.11.0",
45
44
  "asciichart": "^1.5.25",
46
45
  "chalk": "^4.1.2",
47
46
  "commander": "^11.1.0",
48
47
  "figlet": "^1.7.0",
48
+ "inquirer": "^8.2.7",
49
49
  "ora": "^5.4.1",
50
50
  "protobufjs": "^8.0.0",
51
51
  "ws": "^8.18.3"
package/src/app.js CHANGED
@@ -133,7 +133,7 @@ const banner = async () => {
133
133
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
134
134
  const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
135
135
  console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
136
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
136
+ // No closing line - dashboard will continue the box
137
137
  };
138
138
 
139
139
  /**
@@ -142,20 +142,36 @@ const banner = async () => {
142
142
  const mainMenu = async () => {
143
143
  const boxWidth = getLogoWidth();
144
144
  const innerWidth = boxWidth - 2;
145
+ const col1Width = Math.floor(innerWidth / 2);
145
146
 
146
- console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
147
+ const menuRow = (left, right) => {
148
+ const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
149
+ const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
150
+ const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
151
+ const rightPadded = (right || '') + ' '.repeat(Math.max(0, innerWidth - col1Width - rightPlain.length));
152
+ console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
153
+ };
154
+
155
+ // Continue from banner
156
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
147
157
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PLATFORM', innerWidth)) + chalk.cyan('║'));
148
158
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
159
+
160
+ menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
161
+ menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Exit'));
162
+
149
163
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
150
164
 
151
- const action = await prompts.selectOption('Select platform:', [
152
- { value: 'projectx', label: '[1] ProjectX' },
153
- { value: 'rithmic', label: '[2] Rithmic' },
154
- { value: 'tradovate', label: '[3] Tradovate' },
155
- { value: 'exit', label: '[X] Exit' }
156
- ]);
165
+ const input = await prompts.textInput('Select (1/2/3/X)');
166
+
167
+ const actionMap = {
168
+ '1': 'projectx',
169
+ '2': 'rithmic',
170
+ '3': 'tradovate',
171
+ 'x': 'exit'
172
+ };
157
173
 
158
- return action || 'exit';
174
+ return actionMap[(input || '').toLowerCase()] || 'exit';
159
175
  };
160
176
 
161
177
  /**
@@ -30,7 +30,8 @@ const dashboardMenu = async (service) => {
30
30
  return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
31
31
  };
32
32
 
33
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
33
+ // Continue from banner (no top border)
34
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
34
35
  console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
35
36
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
36
37
 
@@ -89,16 +90,19 @@ const dashboardMenu = async (service) => {
89
90
 
90
91
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
91
92
 
92
- const action = await prompts.selectOption('Select:', [
93
- { value: 'accounts', label: '[1] View Accounts' },
94
- { value: 'stats', label: '[2] View Stats' },
95
- { value: 'add_prop_account', label: '[+] Add Prop-Account' },
96
- { value: 'algotrading', label: '[A] Algo-Trading' },
97
- { value: 'update', label: '[U] Update HQX' },
98
- { value: 'disconnect', label: '[X] Disconnect' }
99
- ]);
93
+ // Simple input - no duplicate menu
94
+ const input = await prompts.textInput('Select (1/2/+/A/U/X)');
95
+
96
+ const actionMap = {
97
+ '1': 'accounts',
98
+ '2': 'stats',
99
+ '+': 'add_prop_account',
100
+ 'a': 'algotrading',
101
+ 'u': 'update',
102
+ 'x': 'disconnect'
103
+ };
100
104
 
101
- return action || 'disconnect';
105
+ return actionMap[(input || '').toLowerCase()] || null;
102
106
  };
103
107
 
104
108
  /**
@@ -1,87 +1,127 @@
1
1
  /**
2
- * Centralized prompts utility using @clack/prompts
3
- * Replaces inquirer throughout the app
2
+ * Centralized prompts utility
3
+ * Uses inquirer for reliable stdin handling
4
4
  */
5
5
 
6
- const { select, text, password, confirm, isCancel } = require('@clack/prompts');
7
- const readline = require('readline');
6
+ const inquirer = require('inquirer');
8
7
 
9
8
  /**
10
- * Wait for Enter key
9
+ * Ensure stdin is ready
11
10
  */
12
- const waitForEnter = (message = 'Press Enter to continue...') => new Promise(resolve => {
13
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
14
- rl.question(message, () => { rl.close(); resolve(); });
15
- });
11
+ const prepareStdin = () => {
12
+ try {
13
+ if (process.stdin.isPaused()) process.stdin.resume();
14
+ if (process.stdin.isTTY && process.stdin.isRaw) process.stdin.setRawMode(false);
15
+ } catch (e) {}
16
+ };
16
17
 
17
18
  /**
18
- * Select from list
19
+ * Wait for Enter
19
20
  */
20
- const selectOption = async (message, options) => {
21
- const result = await select({ message, options });
22
- if (isCancel(result)) return null;
23
- return result;
21
+ const waitForEnter = async (message = 'Press Enter to continue...') => {
22
+ prepareStdin();
23
+ await inquirer.prompt([{ type: 'input', name: '_', message, prefix: '' }]);
24
24
  };
25
25
 
26
26
  /**
27
27
  * Text input
28
28
  */
29
- const textInput = async (message, initialValue = '', validate = null) => {
30
- const opts = { message };
31
- if (initialValue) opts.initialValue = initialValue;
32
- if (validate) opts.validate = validate;
33
-
34
- const result = await text(opts);
35
- if (isCancel(result)) return null;
36
- return result;
29
+ const textInput = async (message, defaultVal = '') => {
30
+ prepareStdin();
31
+ const { value } = await inquirer.prompt([{
32
+ type: 'input',
33
+ name: 'value',
34
+ message,
35
+ default: defaultVal,
36
+ prefix: ''
37
+ }]);
38
+ return value;
37
39
  };
38
40
 
39
41
  /**
40
42
  * Password input
41
43
  */
42
- const passwordInput = async (message, validate = null) => {
43
- const opts = { message };
44
- if (validate) opts.validate = validate;
45
-
46
- const result = await password(opts);
47
- if (isCancel(result)) return null;
48
- return result;
44
+ const passwordInput = async (message) => {
45
+ prepareStdin();
46
+ const { value } = await inquirer.prompt([{
47
+ type: 'password',
48
+ name: 'value',
49
+ message,
50
+ mask: '*',
51
+ prefix: ''
52
+ }]);
53
+ return value;
49
54
  };
50
55
 
51
56
  /**
52
- * Confirm yes/no
57
+ * Confirm Y/n
53
58
  */
54
- const confirmPrompt = async (message, initial = true) => {
55
- const result = await confirm({ message, initialValue: initial });
56
- if (isCancel(result)) return null;
57
- return result;
59
+ const confirmPrompt = async (message, defaultVal = true) => {
60
+ prepareStdin();
61
+ const { value } = await inquirer.prompt([{
62
+ type: 'confirm',
63
+ name: 'value',
64
+ message,
65
+ default: defaultVal,
66
+ prefix: ''
67
+ }]);
68
+ return value;
58
69
  };
59
70
 
60
71
  /**
61
- * Number input with validation
72
+ * Number input
62
73
  */
63
74
  const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
64
- const result = await text({
75
+ prepareStdin();
76
+ const { value } = await inquirer.prompt([{
77
+ type: 'input',
78
+ name: 'value',
65
79
  message,
66
- initialValue: String(defaultVal),
67
- validate: v => {
80
+ default: String(defaultVal),
81
+ prefix: '',
82
+ validate: (v) => {
68
83
  const n = parseInt(v);
69
84
  if (isNaN(n)) return 'Enter a number';
70
- if (n < min) return `Minimum is ${min}`;
71
- if (n > max) return `Maximum is ${max}`;
72
- return undefined;
85
+ if (n < min) return `Min: ${min}`;
86
+ if (n > max) return `Max: ${max}`;
87
+ return true;
73
88
  }
74
- });
75
- if (isCancel(result)) return null;
76
- return parseInt(result);
89
+ }]);
90
+ return parseInt(value) || defaultVal;
91
+ };
92
+
93
+ /**
94
+ * Select - just text input, map to value
95
+ */
96
+ const selectOption = async (message, options) => {
97
+ prepareStdin();
98
+ const { value } = await inquirer.prompt([{
99
+ type: 'input',
100
+ name: 'value',
101
+ message,
102
+ prefix: ''
103
+ }]);
104
+
105
+ const input = (value || '').toLowerCase().trim();
106
+
107
+ // Find by value
108
+ for (const opt of options) {
109
+ if (String(opt.value).toLowerCase() === input) return opt.value;
110
+ }
111
+
112
+ // Find by index (1-based)
113
+ const idx = parseInt(input) - 1;
114
+ if (idx >= 0 && idx < options.length) return options[idx].value;
115
+
116
+ return null;
77
117
  };
78
118
 
79
119
  module.exports = {
120
+ prepareStdin,
80
121
  waitForEnter,
81
- selectOption,
82
122
  textInput,
83
123
  passwordInput,
84
124
  confirmPrompt,
85
125
  numberInput,
86
- isCancel
126
+ selectOption
87
127
  };