hedgequantx 1.7.8 → 1.8.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.7.8",
3
+ "version": "1.8.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": {
@@ -45,6 +45,7 @@
45
45
  "chalk": "^4.1.2",
46
46
  "commander": "^11.1.0",
47
47
  "figlet": "^1.7.0",
48
+ "inquirer": "^8.2.7",
48
49
  "ora": "^5.4.1",
49
50
  "protobufjs": "^8.0.0",
50
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
- // No closing line - dashboard will continue the box
136
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
137
137
  };
138
138
 
139
139
  /**
@@ -152,8 +152,7 @@ const mainMenu = async () => {
152
152
  console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
153
153
  };
154
154
 
155
- // Continue from banner
156
- console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
155
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
157
156
  console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PLATFORM', innerWidth)) + chalk.cyan('║'));
158
157
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
159
158
 
@@ -30,8 +30,7 @@ const dashboardMenu = async (service) => {
30
30
  return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
31
31
  };
32
32
 
33
- // Continue from banner (no top border)
34
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
33
+ console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
35
34
  console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
36
35
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
37
36
 
@@ -1,195 +1,127 @@
1
1
  /**
2
- * Centralized prompts utility - lightweight, no external UI
3
- * Uses readline for simple input, keeps our custom box design
2
+ * Centralized prompts utility
3
+ * Uses inquirer for reliable stdin handling
4
4
  */
5
5
 
6
- const readline = require('readline');
6
+ const inquirer = require('inquirer');
7
7
 
8
8
  /**
9
- * Create readline interface
9
+ * Ensure stdin is ready
10
10
  */
11
- const createRL = () => readline.createInterface({
12
- input: process.stdin,
13
- output: process.stdout
14
- });
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
+ };
15
17
 
16
18
  /**
17
- * Wait for Enter key
19
+ * Wait for Enter
18
20
  */
19
- const waitForEnter = (message = 'Press Enter to continue...') => new Promise(resolve => {
20
- const rl = createRL();
21
- rl.question(message, () => { rl.close(); resolve(); });
22
- });
21
+ const waitForEnter = async (message = 'Press Enter to continue...') => {
22
+ prepareStdin();
23
+ await inquirer.prompt([{ type: 'input', name: '_', message, prefix: '' }]);
24
+ };
23
25
 
24
26
  /**
25
- * Simple text input
27
+ * Text input
26
28
  */
27
- const textInput = (message, defaultVal = '') => new Promise(resolve => {
28
- const rl = createRL();
29
- const prompt = defaultVal ? `${message} (${defaultVal}): ` : `${message}: `;
30
- rl.question(prompt, (answer) => {
31
- rl.close();
32
- resolve(answer.trim() || defaultVal);
33
- });
34
- });
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;
39
+ };
35
40
 
36
41
  /**
37
- * Password input (hidden)
42
+ * Password input
38
43
  */
39
- const passwordInput = (message) => new Promise(resolve => {
40
- const rl = createRL();
41
- process.stdout.write(`${message}: `);
42
-
43
- if (process.stdin.isTTY) {
44
- process.stdin.setRawMode(true);
45
- }
46
-
47
- let password = '';
48
- const onData = (char) => {
49
- char = char.toString();
50
-
51
- if (char === '\n' || char === '\r') {
52
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
53
- process.stdin.removeListener('data', onData);
54
- console.log();
55
- rl.close();
56
- resolve(password);
57
- } else if (char === '\u0003') { // Ctrl+C
58
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
59
- process.stdin.removeListener('data', onData);
60
- rl.close();
61
- resolve(null);
62
- } else if (char === '\u007F' || char === '\b') { // Backspace
63
- if (password.length > 0) {
64
- password = password.slice(0, -1);
65
- process.stdout.write('\b \b');
66
- }
67
- } else {
68
- password += char;
69
- process.stdout.write('*');
70
- }
71
- };
72
-
73
- process.stdin.on('data', onData);
74
- process.stdin.resume();
75
- });
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;
54
+ };
76
55
 
77
56
  /**
78
- * Select from options using arrow keys
57
+ * Confirm Y/n
79
58
  */
80
- const selectOption = (message, options) => new Promise(resolve => {
81
- if (!process.stdin.isTTY) {
82
- // Fallback for non-TTY
83
- const rl = createRL();
84
- console.log(message);
85
- options.forEach((opt, i) => console.log(` ${i + 1}. ${opt.label}`));
86
- rl.question('Enter number: ', (answer) => {
87
- rl.close();
88
- const idx = parseInt(answer) - 1;
89
- resolve(options[idx]?.value || null);
90
- });
91
- return;
92
- }
93
-
94
- let selectedIndex = 0;
95
- const maxIndex = options.length - 1;
96
-
97
- const render = () => {
98
- // Move cursor up and clear lines
99
- if (selectedIndex > 0 || options.length > 1) {
100
- process.stdout.write(`\x1B[${options.length}A`);
101
- }
102
-
103
- options.forEach((opt, i) => {
104
- const prefix = i === selectedIndex ? '› ' : ' ';
105
- const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m'; // cyan : gray
106
- process.stdout.write(`\x1B[2K${style}${prefix}${opt.label}\x1B[0m\n`);
107
- });
108
- };
109
-
110
- // Initial render
111
- console.log(`\x1B[36m${message}\x1B[0m`);
112
- options.forEach((opt, i) => {
113
- const prefix = i === selectedIndex ? '› ' : ' ';
114
- const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m';
115
- console.log(`${style}${prefix}${opt.label}\x1B[0m`);
116
- });
117
-
118
- readline.emitKeypressEvents(process.stdin);
119
- process.stdin.setRawMode(true);
120
- process.stdin.resume();
121
-
122
- const onKeypress = (str, key) => {
123
- if (key.name === 'up' || key.name === 'k') {
124
- selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : maxIndex;
125
- render();
126
- } else if (key.name === 'down' || key.name === 'j') {
127
- selectedIndex = selectedIndex < maxIndex ? selectedIndex + 1 : 0;
128
- render();
129
- } else if (key.name === 'return') {
130
- cleanup();
131
- resolve(options[selectedIndex].value);
132
- } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
133
- cleanup();
134
- resolve(null);
135
- } else if (str >= '1' && str <= '9') {
136
- const idx = parseInt(str) - 1;
137
- if (idx <= maxIndex) {
138
- cleanup();
139
- resolve(options[idx].value);
140
- }
141
- }
142
- };
143
-
144
- const cleanup = () => {
145
- process.stdin.removeListener('keypress', onKeypress);
146
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
147
- // Clear the menu display
148
- process.stdout.write(`\x1B[${options.length + 1}A`);
149
- for (let i = 0; i <= options.length; i++) {
150
- process.stdout.write('\x1B[2K\n');
151
- }
152
- process.stdout.write(`\x1B[${options.length + 1}A`);
153
- };
154
-
155
- process.stdin.on('keypress', onKeypress);
156
- });
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;
69
+ };
157
70
 
158
71
  /**
159
- * Confirm yes/no
72
+ * Number input
160
73
  */
161
- const confirmPrompt = (message, defaultVal = true) => new Promise(resolve => {
162
- const rl = createRL();
163
- const hint = defaultVal ? '(Y/n)' : '(y/N)';
164
- rl.question(`${message} ${hint}: `, (answer) => {
165
- rl.close();
166
- const a = answer.toLowerCase().trim();
167
- if (a === '') resolve(defaultVal);
168
- else if (a === 'y' || a === 'yes') resolve(true);
169
- else if (a === 'n' || a === 'no') resolve(false);
170
- else resolve(defaultVal);
171
- });
172
- });
74
+ const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
75
+ prepareStdin();
76
+ const { value } = await inquirer.prompt([{
77
+ type: 'input',
78
+ name: 'value',
79
+ message,
80
+ default: String(defaultVal),
81
+ prefix: '',
82
+ validate: (v) => {
83
+ const n = parseInt(v);
84
+ if (isNaN(n)) return 'Enter a number';
85
+ if (n < min) return `Min: ${min}`;
86
+ if (n > max) return `Max: ${max}`;
87
+ return true;
88
+ }
89
+ }]);
90
+ return parseInt(value) || defaultVal;
91
+ };
173
92
 
174
93
  /**
175
- * Number input
94
+ * Select - just text input, map to value
176
95
  */
177
- const numberInput = (message, defaultVal = 1, min = 1, max = 1000) => new Promise(resolve => {
178
- const rl = createRL();
179
- rl.question(`${message} (${defaultVal}): `, (answer) => {
180
- rl.close();
181
- const n = parseInt(answer) || defaultVal;
182
- if (n < min) resolve(min);
183
- else if (n > max) resolve(max);
184
- else resolve(n);
185
- });
186
- });
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;
117
+ };
187
118
 
188
119
  module.exports = {
120
+ prepareStdin,
189
121
  waitForEnter,
190
- selectOption,
191
122
  textInput,
192
123
  passwordInput,
193
124
  confirmPrompt,
194
- numberInput
125
+ numberInput,
126
+ selectOption
195
127
  };