hedgequantx 1.2.145 → 1.2.147

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.
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Connection Menus - PropFirm platform selection and login
3
+ * Handles ProjectX, Rithmic, and Tradovate connections
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const inquirer = require('inquirer');
8
+ const ora = require('ora');
9
+
10
+ const { ProjectXService, connections } = require('../services');
11
+ const { RithmicService } = require('../services/rithmic');
12
+ const { TradovateService } = require('../services/tradovate');
13
+ const { getPropFirmsByPlatform } = require('../config');
14
+ const { getDevice, getLogoWidth, centerText } = require('../ui');
15
+ const { validateUsername, validatePassword } = require('../security');
16
+
17
+ /**
18
+ * Login prompt with validation
19
+ * @param {string} propfirmName - PropFirm display name
20
+ * @returns {Promise<{username: string, password: string}>}
21
+ */
22
+ const loginPrompt = async (propfirmName) => {
23
+ const device = getDevice();
24
+ console.log();
25
+ console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
26
+ console.log();
27
+
28
+ const credentials = await inquirer.prompt([
29
+ {
30
+ type: 'input',
31
+ name: 'username',
32
+ message: chalk.white.bold('Username:'),
33
+ validate: (input) => {
34
+ try {
35
+ validateUsername(input);
36
+ return true;
37
+ } catch (e) {
38
+ return e.message;
39
+ }
40
+ }
41
+ },
42
+ {
43
+ type: 'password',
44
+ name: 'password',
45
+ message: chalk.white.bold('Password:'),
46
+ mask: '*',
47
+ validate: (input) => {
48
+ try {
49
+ validatePassword(input);
50
+ return true;
51
+ } catch (e) {
52
+ return e.message;
53
+ }
54
+ }
55
+ }
56
+ ]);
57
+
58
+ return credentials;
59
+ };
60
+
61
+ /**
62
+ * ProjectX platform connection menu
63
+ */
64
+ const projectXMenu = async () => {
65
+ const propfirms = getPropFirmsByPlatform('ProjectX');
66
+ const boxWidth = getLogoWidth();
67
+ const innerWidth = boxWidth - 2;
68
+ const numCols = 3;
69
+ const colWidth = Math.floor(innerWidth / numCols);
70
+
71
+ // Build numbered list
72
+ const numbered = propfirms.map((pf, i) => ({
73
+ num: i + 1,
74
+ key: pf.key,
75
+ name: pf.displayName
76
+ }));
77
+
78
+ // PropFirm selection box
79
+ console.log();
80
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
81
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
82
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
83
+
84
+ // Display in 3 columns with fixed width alignment
85
+ const rows = Math.ceil(numbered.length / numCols);
86
+ const maxNum = numbered.length;
87
+ const numWidth = maxNum >= 10 ? 4 : 3; // [XX] or [X]
88
+
89
+ for (let row = 0; row < rows; row++) {
90
+ let line = '';
91
+ for (let col = 0; col < numCols; col++) {
92
+ const idx = row + col * rows;
93
+ if (idx < numbered.length) {
94
+ const item = numbered[idx];
95
+ const numStr = item.num.toString().padStart(2, ' ');
96
+ const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
97
+ const textLen = 4 + 1 + item.name.length; // [XX] + space + name
98
+ const padding = colWidth - textLen - 2;
99
+ line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
100
+ } else {
101
+ line += ' '.repeat(colWidth);
102
+ }
103
+ }
104
+ // Adjust for exact width
105
+ const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
106
+ const adjust = innerWidth - lineLen;
107
+ console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
108
+ }
109
+
110
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
111
+ const backText = ' ' + chalk.red('[X] Back');
112
+ const backLen = '[X] Back'.length + 2;
113
+ console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
114
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
115
+ console.log();
116
+
117
+ const validInputs = numbered.map(n => n.num.toString());
118
+ validInputs.push('x', 'X');
119
+
120
+ const { action } = await inquirer.prompt([
121
+ {
122
+ type: 'input',
123
+ name: 'action',
124
+ message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
125
+ validate: (input) => {
126
+ if (validInputs.includes(input)) return true;
127
+ return `Please enter 1-${numbered.length} or X`;
128
+ }
129
+ }
130
+ ]);
131
+
132
+ if (action.toLowerCase() === 'x') return null;
133
+
134
+ const selectedIdx = parseInt(action) - 1;
135
+ const selectedPropfirm = numbered[selectedIdx];
136
+
137
+ const credentials = await loginPrompt(selectedPropfirm.name);
138
+ const spinner = ora('Authenticating...').start();
139
+
140
+ try {
141
+ const service = new ProjectXService(selectedPropfirm.key);
142
+ const result = await service.login(credentials.username, credentials.password);
143
+
144
+ if (result.success) {
145
+ await service.getUser();
146
+ connections.add('projectx', service, service.propfirm.name);
147
+ spinner.succeed(`Connected to ${service.propfirm.name}`);
148
+ return service;
149
+ } else {
150
+ spinner.fail(result.error || 'Authentication failed');
151
+ return null;
152
+ }
153
+ } catch (error) {
154
+ spinner.fail(error.message);
155
+ return null;
156
+ }
157
+ };
158
+
159
+ /**
160
+ * Rithmic platform connection menu
161
+ */
162
+ const rithmicMenu = async () => {
163
+ const propfirms = getPropFirmsByPlatform('Rithmic');
164
+ const boxWidth = getLogoWidth();
165
+ const innerWidth = boxWidth - 2;
166
+ const numCols = 3;
167
+ const colWidth = Math.floor(innerWidth / numCols);
168
+
169
+ // Build numbered list
170
+ const numbered = propfirms.map((pf, i) => ({
171
+ num: i + 1,
172
+ key: pf.key,
173
+ name: pf.displayName,
174
+ systemName: pf.rithmicSystem
175
+ }));
176
+
177
+ // PropFirm selection box
178
+ console.log();
179
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
180
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (RITHMIC)', innerWidth)) + chalk.cyan('║'));
181
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
182
+
183
+ // Display in 3 columns with fixed width alignment
184
+ const rows = Math.ceil(numbered.length / numCols);
185
+
186
+ for (let row = 0; row < rows; row++) {
187
+ let line = '';
188
+ for (let col = 0; col < numCols; col++) {
189
+ const idx = row + col * rows;
190
+ if (idx < numbered.length) {
191
+ const item = numbered[idx];
192
+ const numStr = item.num.toString().padStart(2, ' ');
193
+ const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
194
+ const textLen = 4 + 1 + item.name.length;
195
+ const padding = colWidth - textLen - 2;
196
+ line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
197
+ } else {
198
+ line += ' '.repeat(colWidth);
199
+ }
200
+ }
201
+ const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
202
+ const adjust = innerWidth - lineLen;
203
+ console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
204
+ }
205
+
206
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
207
+ const backText = ' ' + chalk.red('[X] Back');
208
+ const backLen = '[X] Back'.length + 2;
209
+ console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
210
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
211
+ console.log();
212
+
213
+ const validInputs = numbered.map(n => n.num.toString());
214
+ validInputs.push('x', 'X');
215
+
216
+ const { action } = await inquirer.prompt([
217
+ {
218
+ type: 'input',
219
+ name: 'action',
220
+ message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
221
+ validate: (input) => {
222
+ if (validInputs.includes(input)) return true;
223
+ return `Please enter 1-${numbered.length} or X`;
224
+ }
225
+ }
226
+ ]);
227
+
228
+ if (action.toLowerCase() === 'x') return null;
229
+
230
+ const selectedIdx = parseInt(action) - 1;
231
+ const selectedPropfirm = numbered[selectedIdx];
232
+
233
+ const credentials = await loginPrompt(selectedPropfirm.name);
234
+ const spinner = ora('Connecting to Rithmic...').start();
235
+
236
+ try {
237
+ const service = new RithmicService(selectedPropfirm.key);
238
+ const result = await service.login(credentials.username, credentials.password);
239
+
240
+ if (result.success) {
241
+ spinner.text = 'Fetching accounts...';
242
+ const accResult = await service.getTradingAccounts();
243
+
244
+ connections.add('rithmic', service, service.propfirm.name);
245
+ spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
246
+
247
+ // Small pause to see the success message
248
+ await new Promise(r => setTimeout(r, 1500));
249
+ return service;
250
+ } else {
251
+ spinner.fail(result.error || 'Authentication failed');
252
+ await new Promise(r => setTimeout(r, 2000));
253
+ return null;
254
+ }
255
+ } catch (error) {
256
+ spinner.fail(`Connection error: ${error.message}`);
257
+ await new Promise(r => setTimeout(r, 2000));
258
+ return null;
259
+ }
260
+ };
261
+
262
+ /**
263
+ * Tradovate platform connection menu
264
+ */
265
+ const tradovateMenu = async () => {
266
+ const propfirms = getPropFirmsByPlatform('Tradovate');
267
+ const boxWidth = getLogoWidth();
268
+ const innerWidth = boxWidth - 2;
269
+
270
+ // Build numbered list
271
+ const numbered = propfirms.map((pf, i) => ({
272
+ num: i + 1,
273
+ key: pf.key,
274
+ name: pf.displayName
275
+ }));
276
+
277
+ // PropFirm selection box
278
+ console.log();
279
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
280
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (TRADOVATE)', innerWidth)) + chalk.cyan('║'));
281
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
282
+
283
+ // Display propfirms
284
+ for (const item of numbered) {
285
+ const numStr = item.num.toString().padStart(2, ' ');
286
+ const text = ' ' + chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
287
+ const textLen = 4 + 1 + item.name.length + 2;
288
+ console.log(chalk.cyan('║') + text + ' '.repeat(innerWidth - textLen) + chalk.cyan('║'));
289
+ }
290
+
291
+ console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
292
+ const backText = ' ' + chalk.red('[X] Back');
293
+ const backLen = '[X] Back'.length + 2;
294
+ console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
295
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
296
+ console.log();
297
+
298
+ const validInputs = numbered.map(n => n.num.toString());
299
+ validInputs.push('x', 'X');
300
+
301
+ const { action } = await inquirer.prompt([
302
+ {
303
+ type: 'input',
304
+ name: 'action',
305
+ message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
306
+ validate: (input) => {
307
+ if (validInputs.includes(input)) return true;
308
+ return `Please enter 1-${numbered.length} or X`;
309
+ }
310
+ }
311
+ ]);
312
+
313
+ if (action.toLowerCase() === 'x') return null;
314
+
315
+ const selectedIdx = parseInt(action) - 1;
316
+ const selectedPropfirm = numbered[selectedIdx];
317
+
318
+ const credentials = await loginPrompt(selectedPropfirm.name);
319
+ const spinner = ora('Connecting to Tradovate...').start();
320
+
321
+ try {
322
+ const service = new TradovateService(selectedPropfirm.key);
323
+ const result = await service.login(credentials.username, credentials.password);
324
+
325
+ if (result.success) {
326
+ spinner.text = 'Fetching accounts...';
327
+ await service.getTradingAccounts();
328
+
329
+ connections.add('tradovate', service, service.propfirm.name);
330
+ spinner.succeed(`Connected to ${service.propfirm.name}`);
331
+ return service;
332
+ } else {
333
+ spinner.fail(result.error || 'Authentication failed');
334
+ return null;
335
+ }
336
+ } catch (error) {
337
+ spinner.fail(error.message);
338
+ return null;
339
+ }
340
+ };
341
+
342
+ /**
343
+ * Add Prop Account menu (select platform)
344
+ */
345
+ const addPropAccountMenu = async () => {
346
+ const boxWidth = getLogoWidth();
347
+ const innerWidth = boxWidth - 2;
348
+ const col1Width = Math.floor(innerWidth / 2);
349
+ const col2Width = innerWidth - col1Width;
350
+
351
+ console.log();
352
+ console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
353
+ console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT', innerWidth)) + chalk.cyan('║'));
354
+ console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
355
+
356
+ const menuRow = (left, right) => {
357
+ const leftText = ' ' + left;
358
+ const rightText = right ? ' ' + right : '';
359
+ const leftLen = leftText.replace(/\x1b\[[0-9;]*m/g, '').length;
360
+ const rightLen = rightText.replace(/\x1b\[[0-9;]*m/g, '').length;
361
+ const leftPad = col1Width - leftLen;
362
+ const rightPad = col2Width - rightLen;
363
+ console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
364
+ };
365
+
366
+ menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
367
+ menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Back'));
368
+
369
+ console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
370
+ console.log();
371
+
372
+ const { action } = await inquirer.prompt([
373
+ {
374
+ type: 'input',
375
+ name: 'action',
376
+ message: chalk.cyan('Enter choice (1/2/3/X):'),
377
+ validate: (input) => {
378
+ const valid = ['1', '2', '3', 'x', 'X'];
379
+ if (valid.includes(input)) return true;
380
+ return 'Please enter 1, 2, 3 or X';
381
+ }
382
+ }
383
+ ]);
384
+
385
+ const actionMap = {
386
+ '1': 'projectx',
387
+ '2': 'rithmic',
388
+ '3': 'tradovate',
389
+ 'x': null,
390
+ 'X': null
391
+ };
392
+
393
+ return actionMap[action];
394
+ };
395
+
396
+ module.exports = {
397
+ loginPrompt,
398
+ projectXMenu,
399
+ rithmicMenu,
400
+ tradovateMenu,
401
+ addPropAccountMenu
402
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Dashboard Menu - Main menu after login
3
+ * Shows connected PropFirms and navigation options
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const inquirer = require('inquirer');
8
+ const ora = require('ora');
9
+ const { execSync, spawn } = require('child_process');
10
+
11
+ const { connections } = require('../services');
12
+ const { getLogoWidth, centerText } = require('../ui');
13
+
14
+ /**
15
+ * Dashboard menu after login
16
+ * @param {Object} service - Connected service
17
+ */
18
+ const dashboardMenu = async (service) => {
19
+ const user = service.user;
20
+ const boxWidth = getLogoWidth();
21
+ const W = boxWidth - 2; // Same width as logo (inner width)
22
+
23
+ // Helper to center text
24
+ const centerLine = (text, width) => {
25
+ const pad = Math.floor((width - text.length) / 2);
26
+ return ' '.repeat(Math.max(0, pad)) + text + ' '.repeat(Math.max(0, width - pad - text.length));
27
+ };
28
+
29
+ // Helper to pad text left
30
+ const padLine = (text, width) => {
31
+ return ' ' + text + ' '.repeat(Math.max(0, width - text.length - 1));
32
+ };
33
+
34
+ // Dashboard box header
35
+ console.log();
36
+ console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
37
+ console.log(chalk.cyan('║') + chalk.yellow.bold(centerLine('Welcome, HQX Trader!', W)) + chalk.cyan('║'));
38
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
39
+
40
+ // Connection info - show all active connections in boxes (max 3 per row)
41
+ const allConns = connections.getAll();
42
+ if (allConns.length > 0) {
43
+ const maxPerRow = 3;
44
+ const boxPadding = 2; // padding inside each mini-box
45
+ const gap = 2; // gap between boxes
46
+
47
+ // Calculate box width based on number of connections (max 3)
48
+ const numBoxes = Math.min(allConns.length, maxPerRow);
49
+ const totalGaps = (numBoxes - 1) * gap;
50
+ const connBoxWidth = Math.floor((W - totalGaps - 2) / numBoxes); // -2 for outer padding
51
+
52
+ // Process connections in rows of 3
53
+ for (let rowStart = 0; rowStart < allConns.length; rowStart += maxPerRow) {
54
+ const rowConns = allConns.slice(rowStart, rowStart + maxPerRow);
55
+ const numInRow = rowConns.length;
56
+ const rowBoxWidth = Math.floor((W - (numInRow - 1) * gap - 2) / numInRow);
57
+
58
+ // Top border of boxes
59
+ let topLine = ' ';
60
+ for (let i = 0; i < numInRow; i++) {
61
+ topLine += '┌' + '─'.repeat(rowBoxWidth - 2) + '┐';
62
+ if (i < numInRow - 1) topLine += ' '.repeat(gap);
63
+ }
64
+ const topPad = W - topLine.length;
65
+ console.log(chalk.cyan('║') + chalk.green(topLine) + ' '.repeat(Math.max(0, topPad)) + chalk.cyan('║'));
66
+
67
+ // Content of boxes
68
+ let contentLine = ' ';
69
+ for (let i = 0; i < numInRow; i++) {
70
+ const connText = rowConns[i].propfirm || rowConns[i].type || 'Connected';
71
+ const truncated = connText.length > rowBoxWidth - 4 ? connText.slice(0, rowBoxWidth - 7) + '...' : connText;
72
+ const innerWidth = rowBoxWidth - 4; // -2 for borders, -2 for padding
73
+ const textPad = Math.floor((innerWidth - truncated.length) / 2);
74
+ const textPadRight = innerWidth - truncated.length - textPad;
75
+ contentLine += '│ ' + ' '.repeat(textPad) + truncated + ' '.repeat(textPadRight) + ' │';
76
+ if (i < numInRow - 1) contentLine += ' '.repeat(gap);
77
+ }
78
+ const contentPad = W - contentLine.length;
79
+ console.log(chalk.cyan('║') + chalk.green(contentLine) + ' '.repeat(Math.max(0, contentPad)) + chalk.cyan('║'));
80
+
81
+ // Bottom border of boxes
82
+ let bottomLine = ' ';
83
+ for (let i = 0; i < numInRow; i++) {
84
+ bottomLine += '└' + '─'.repeat(rowBoxWidth - 2) + '┘';
85
+ if (i < numInRow - 1) bottomLine += ' '.repeat(gap);
86
+ }
87
+ const bottomPad = W - bottomLine.length;
88
+ console.log(chalk.cyan('║') + chalk.green(bottomLine) + ' '.repeat(Math.max(0, bottomPad)) + chalk.cyan('║'));
89
+ }
90
+ }
91
+
92
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
93
+
94
+ // Menu options in 2 columns
95
+ const col1Width = Math.floor(W / 2);
96
+ const col2Width = W - col1Width;
97
+
98
+ const menuRow = (left, right) => {
99
+ const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
100
+ const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
101
+ const leftPad = ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
102
+ const rightPad = ' '.repeat(Math.max(0, col2Width - rightPlain.length - 2));
103
+ console.log(chalk.cyan('║') + ' ' + left + leftPad + ' ' + (right || '') + rightPad + chalk.cyan('║'));
104
+ };
105
+
106
+ menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
107
+ menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.cyan('[A] Algo-Trading'));
108
+ menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
109
+
110
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
111
+ console.log();
112
+
113
+ const { action } = await inquirer.prompt([
114
+ {
115
+ type: 'input',
116
+ name: 'action',
117
+ message: chalk.cyan('Enter choice (1/2/+/A/U/X):'),
118
+ validate: (input) => {
119
+ const valid = ['1', '2', '+', 'a', 'A', 'u', 'U', 'x', 'X'];
120
+ if (valid.includes(input)) return true;
121
+ return 'Please enter a valid option';
122
+ }
123
+ }
124
+ ]);
125
+
126
+ // Map input to action
127
+ const actionMap = {
128
+ '1': 'accounts',
129
+ '2': 'stats',
130
+ '+': 'add_prop_account',
131
+ 'a': 'algotrading',
132
+ 'A': 'algotrading',
133
+ 'u': 'update',
134
+ 'U': 'update',
135
+ 'x': 'disconnect',
136
+ 'X': 'disconnect'
137
+ };
138
+
139
+ return actionMap[action] || 'accounts';
140
+ };
141
+
142
+ /**
143
+ * Handles the update process with auto-restart
144
+ */
145
+ const handleUpdate = async () => {
146
+ const pkg = require('../../package.json');
147
+ const currentVersion = pkg.version;
148
+ const spinner = ora('Checking for updates...').start();
149
+
150
+ try {
151
+ // Check latest version on npm
152
+ spinner.text = 'Checking npm registry...';
153
+ let latestVersion;
154
+ try {
155
+ latestVersion = execSync('npm view hedgequantx version', { stdio: 'pipe' }).toString().trim();
156
+ } catch (e) {
157
+ spinner.fail('Cannot reach npm registry');
158
+ console.log();
159
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
160
+ return;
161
+ }
162
+
163
+ if (currentVersion === latestVersion) {
164
+ spinner.succeed('Already up to date!');
165
+ console.log();
166
+ console.log(chalk.green(` ✓ You have the latest version of HedgeQuantX CLI: v${currentVersion}`));
167
+ console.log();
168
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
169
+ return;
170
+ }
171
+
172
+ // Update via npm
173
+ spinner.text = `Updating v${currentVersion} -> v${latestVersion}...`;
174
+ try {
175
+ execSync('npm install -g hedgequantx@latest', { stdio: 'pipe' });
176
+ } catch (e) {
177
+ spinner.fail('Update failed - try manually: npm install -g hedgequantx@latest');
178
+ console.log(chalk.gray(` Error: ${e.message}`));
179
+ console.log();
180
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
181
+ return;
182
+ }
183
+
184
+ spinner.succeed('CLI updated!');
185
+ console.log();
186
+ console.log(chalk.green(` ✓ Updated: v${currentVersion} -> v${latestVersion}`));
187
+ console.log();
188
+ console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
189
+ console.log();
190
+
191
+ // Small delay so user can see the message
192
+ await new Promise(resolve => setTimeout(resolve, 1500));
193
+
194
+ // Restart the CLI automatically
195
+ const child = spawn('hedgequantx', [], {
196
+ stdio: 'inherit',
197
+ detached: true,
198
+ shell: true
199
+ });
200
+ child.unref();
201
+ process.exit(0);
202
+
203
+ } catch (error) {
204
+ spinner.fail('Update failed: ' + error.message);
205
+ console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
206
+ console.log();
207
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
208
+ }
209
+ };
210
+
211
+ module.exports = {
212
+ dashboardMenu,
213
+ handleUpdate
214
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Menus - Export all menu modules
3
+ */
4
+
5
+ module.exports = {
6
+ ...require('./connect'),
7
+ ...require('./dashboard')
8
+ };