hedgequantx 1.7.7 → 1.7.8

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.8",
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,7 +41,6 @@
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",
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,80 +1,189 @@
1
1
  /**
2
- * Centralized prompts utility using @clack/prompts
3
- * Replaces inquirer throughout the app
2
+ * Centralized prompts utility - lightweight, no external UI
3
+ * Uses readline for simple input, keeps our custom box design
4
4
  */
5
5
 
6
- const { select, text, password, confirm, isCancel } = require('@clack/prompts');
7
6
  const readline = require('readline');
8
7
 
8
+ /**
9
+ * Create readline interface
10
+ */
11
+ const createRL = () => readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout
14
+ });
15
+
9
16
  /**
10
17
  * Wait for Enter key
11
18
  */
12
19
  const waitForEnter = (message = 'Press Enter to continue...') => new Promise(resolve => {
13
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
20
+ const rl = createRL();
14
21
  rl.question(message, () => { rl.close(); resolve(); });
15
22
  });
16
23
 
17
24
  /**
18
- * Select from list
25
+ * Simple text input
19
26
  */
20
- const selectOption = async (message, options) => {
21
- const result = await select({ message, options });
22
- if (isCancel(result)) return null;
23
- return result;
24
- };
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
+ });
25
35
 
26
36
  /**
27
- * Text input
37
+ * Password input (hidden)
28
38
  */
29
- const textInput = async (message, initialValue = '', validate = null) => {
30
- const opts = { message };
31
- if (initialValue) opts.initialValue = initialValue;
32
- if (validate) opts.validate = validate;
39
+ const passwordInput = (message) => new Promise(resolve => {
40
+ const rl = createRL();
41
+ process.stdout.write(`${message}: `);
33
42
 
34
- const result = await text(opts);
35
- if (isCancel(result)) return null;
36
- return result;
37
- };
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
+ });
38
76
 
39
77
  /**
40
- * Password input
78
+ * Select from options using arrow keys
41
79
  */
42
- const passwordInput = async (message, validate = null) => {
43
- const opts = { message };
44
- if (validate) opts.validate = validate;
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
+ }
45
93
 
46
- const result = await password(opts);
47
- if (isCancel(result)) return null;
48
- return result;
49
- };
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
+ });
50
157
 
51
158
  /**
52
159
  * Confirm yes/no
53
160
  */
54
- const confirmPrompt = async (message, initial = true) => {
55
- const result = await confirm({ message, initialValue: initial });
56
- if (isCancel(result)) return null;
57
- return result;
58
- };
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
+ });
59
173
 
60
174
  /**
61
- * Number input with validation
175
+ * Number input
62
176
  */
63
- const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
64
- const result = await text({
65
- message,
66
- initialValue: String(defaultVal),
67
- validate: v => {
68
- const n = parseInt(v);
69
- 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;
73
- }
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);
74
185
  });
75
- if (isCancel(result)) return null;
76
- return parseInt(result);
77
- };
186
+ });
78
187
 
79
188
  module.exports = {
80
189
  waitForEnter,
@@ -82,6 +191,5 @@ module.exports = {
82
191
  textInput,
83
192
  passwordInput,
84
193
  confirmPrompt,
85
- numberInput,
86
- isCancel
194
+ numberInput
87
195
  };