hedgequantx 2.9.59 → 2.9.60

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/utils/prompts.js +88 -321
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.59",
3
+ "version": "2.9.60",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1,19 +1,15 @@
1
1
  /**
2
- * @fileoverview Centralized prompts utility with animated yellow spinner
2
+ * @fileoverview Centralized prompts utility
3
3
  * @module utils/prompts
4
4
  *
5
- * Custom readline-based prompts with animated spinner that runs
6
- * while waiting for user input. Uses inquirer only for complex
7
- * prompts (password, list selection).
5
+ * Simple prompts using inquirer for user input.
6
+ * No spinners on prompts - spinners are only for data loading (use ora).
8
7
  */
9
8
 
10
9
  const inquirer = require('inquirer');
11
10
  const readline = require('readline');
12
11
  const chalk = require('chalk');
13
12
 
14
- // Spinner frames for yellow waiting indicator
15
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
16
-
17
13
  /** @type {readline.Interface|null} */
18
14
  let rl = null;
19
15
 
@@ -48,305 +44,45 @@ const closeReadline = () => {
48
44
  };
49
45
 
50
46
  /**
51
- * Animated spinner prompt using raw readline
52
- * Spinner animates while waiting for user input
53
- * @param {string} message - Prompt message
54
- * @returns {Promise<string>}
55
- */
56
- const animatedPrompt = (message) => {
57
- return new Promise((resolve) => {
58
- prepareStdin();
59
- closeReadline();
60
-
61
- let frameIndex = 0;
62
- let userInput = '';
63
- let cursorPos = 0;
64
-
65
- // Enable raw mode for character-by-character input
66
- if (process.stdin.isTTY) {
67
- process.stdin.setRawMode(true);
68
- }
69
- process.stdin.resume();
70
-
71
- const render = () => {
72
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
73
- const line = `\r${spinner} ${message} ${userInput}`;
74
- process.stdout.write('\r\x1b[K'); // Clear line
75
- process.stdout.write(line);
76
- };
77
-
78
- // Animate spinner every 80ms
79
- const spinnerInterval = setInterval(() => {
80
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
81
- render();
82
- }, 80);
83
-
84
- render();
85
-
86
- const onData = (key) => {
87
- const char = key.toString();
88
-
89
- // Enter key
90
- if (char === '\r' || char === '\n') {
91
- clearInterval(spinnerInterval);
92
- process.stdin.removeListener('data', onData);
93
- if (process.stdin.isTTY) {
94
- process.stdin.setRawMode(false);
95
- }
96
- process.stdout.write('\n');
97
- resolve(userInput);
98
- return;
99
- }
100
-
101
- // Ctrl+C
102
- if (char === '\x03') {
103
- clearInterval(spinnerInterval);
104
- process.stdin.removeListener('data', onData);
105
- if (process.stdin.isTTY) {
106
- process.stdin.setRawMode(false);
107
- }
108
- process.stdout.write('\n');
109
- process.exit(0);
110
- }
111
-
112
- // Backspace
113
- if (char === '\x7f' || char === '\b') {
114
- if (userInput.length > 0) {
115
- userInput = userInput.slice(0, -1);
116
- render();
117
- }
118
- return;
119
- }
120
-
121
- // Regular printable character
122
- if (char >= ' ' && char <= '~') {
123
- userInput += char;
124
- render();
125
- }
126
- };
127
-
128
- process.stdin.on('data', onData);
129
- });
130
- };
131
-
132
- /**
133
- * Animated Y/N confirm prompt
134
- * Shows [Y/n] or [y/N] based on default
135
- * @param {string} message - Prompt message
136
- * @param {boolean} defaultVal - Default value
137
- * @returns {Promise<boolean>}
138
- */
139
- const animatedConfirm = (message, defaultVal = true) => {
140
- return new Promise((resolve) => {
141
- prepareStdin();
142
- closeReadline();
143
-
144
- let frameIndex = 0;
145
- const hint = defaultVal ? '[Y/n]' : '[y/N]';
146
-
147
- if (process.stdin.isTTY) {
148
- process.stdin.setRawMode(true);
149
- }
150
- process.stdin.resume();
151
-
152
- const render = () => {
153
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
154
- process.stdout.write('\r\x1b[K');
155
- process.stdout.write(`${spinner} ${message} ${chalk.dim(hint)} `);
156
- };
157
-
158
- const spinnerInterval = setInterval(() => {
159
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
160
- render();
161
- }, 80);
162
-
163
- render();
164
-
165
- const onData = (key) => {
166
- const char = key.toString().toLowerCase();
167
-
168
- // Enter = use default
169
- if (char === '\r' || char === '\n') {
170
- cleanup();
171
- process.stdout.write(defaultVal ? 'Yes' : 'No');
172
- process.stdout.write('\n');
173
- resolve(defaultVal);
174
- return;
175
- }
176
-
177
- // Y = yes
178
- if (char === 'y') {
179
- cleanup();
180
- process.stdout.write('Yes');
181
- process.stdout.write('\n');
182
- resolve(true);
183
- return;
184
- }
185
-
186
- // N = no
187
- if (char === 'n') {
188
- cleanup();
189
- process.stdout.write('No');
190
- process.stdout.write('\n');
191
- resolve(false);
192
- return;
193
- }
194
-
195
- // Ctrl+C
196
- if (char === '\x03') {
197
- cleanup();
198
- process.stdout.write('\n');
199
- process.exit(0);
200
- }
201
- };
202
-
203
- const cleanup = () => {
204
- clearInterval(spinnerInterval);
205
- process.stdin.removeListener('data', onData);
206
- if (process.stdin.isTTY) {
207
- process.stdin.setRawMode(false);
208
- }
209
- };
210
-
211
- process.stdin.on('data', onData);
212
- });
213
- };
214
-
215
- /**
216
- * Animated list selection with arrow keys
217
- * @param {string} message - Prompt message
218
- * @param {Array<{name: string, value: any}>} choices - Options
219
- * @returns {Promise<any>}
220
- */
221
- const animatedSelect = (message, choices) => {
222
- return new Promise((resolve) => {
223
- prepareStdin();
224
- closeReadline();
225
-
226
- let frameIndex = 0;
227
- let selectedIndex = 0;
228
- const validChoices = choices.filter(c => !c.disabled);
229
-
230
- if (process.stdin.isTTY) {
231
- process.stdin.setRawMode(true);
232
- }
233
- process.stdin.resume();
234
-
235
- const render = () => {
236
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
237
- // Move cursor to start and clear down
238
- process.stdout.write('\r\x1b[K');
239
- process.stdout.write(`${spinner} ${message}\n`);
240
-
241
- validChoices.forEach((choice, i) => {
242
- process.stdout.write('\x1b[K'); // Clear line
243
- if (i === selectedIndex) {
244
- process.stdout.write(`${chalk.cyan('❯')} ${chalk.cyan(choice.name)}\n`);
245
- } else {
246
- process.stdout.write(` ${choice.name}\n`);
247
- }
248
- });
249
-
250
- // Move cursor back up
251
- process.stdout.write(`\x1b[${validChoices.length + 1}A`);
252
- };
253
-
254
- const spinnerInterval = setInterval(() => {
255
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
256
- render();
257
- }, 80);
258
-
259
- render();
260
-
261
- let escapeSeq = '';
262
-
263
- const onData = (key) => {
264
- const char = key.toString();
265
-
266
- // Handle escape sequences (arrow keys)
267
- if (char === '\x1b') {
268
- escapeSeq = char;
269
- return;
270
- }
271
-
272
- if (escapeSeq === '\x1b' && char === '[') {
273
- escapeSeq += char;
274
- return;
275
- }
276
-
277
- if (escapeSeq === '\x1b[') {
278
- escapeSeq = '';
279
- // Up arrow
280
- if (char === 'A') {
281
- selectedIndex = Math.max(0, selectedIndex - 1);
282
- render();
283
- return;
284
- }
285
- // Down arrow
286
- if (char === 'B') {
287
- selectedIndex = Math.min(validChoices.length - 1, selectedIndex + 1);
288
- render();
289
- return;
290
- }
291
- }
292
-
293
- // Enter = select
294
- if (char === '\r' || char === '\n') {
295
- cleanup();
296
- // Clear the menu lines
297
- process.stdout.write('\r\x1b[K');
298
- for (let i = 0; i < validChoices.length; i++) {
299
- process.stdout.write('\x1b[B\x1b[K');
300
- }
301
- process.stdout.write(`\x1b[${validChoices.length}A`);
302
- process.stdout.write(`${chalk.yellow('⠋')} ${message} ${chalk.cyan(validChoices[selectedIndex].name)}\n`);
303
- resolve(validChoices[selectedIndex].value);
304
- return;
305
- }
306
-
307
- // Ctrl+C
308
- if (char === '\x03') {
309
- cleanup();
310
- process.stdout.write('\n');
311
- process.exit(0);
312
- }
313
- };
314
-
315
- const cleanup = () => {
316
- clearInterval(spinnerInterval);
317
- process.stdin.removeListener('data', onData);
318
- if (process.stdin.isTTY) {
319
- process.stdin.setRawMode(false);
320
- }
321
- };
322
-
323
- process.stdin.on('data', onData);
324
- });
325
- };
326
-
327
- /**
328
- * Wait for Enter key with animated spinner
329
- * @param {string} [message='Press Enter to continue...'] - Message to display
47
+ * Wait for Enter key
48
+ * @param {string} [message='Press Enter to continue...'] - Message
330
49
  * @returns {Promise<void>}
331
50
  */
332
51
  const waitForEnter = async (message = 'Press Enter to continue...') => {
333
- await animatedPrompt(message);
52
+ closeReadline();
53
+ prepareStdin();
54
+
55
+ await inquirer.prompt([{
56
+ type: 'input',
57
+ name: 'continue',
58
+ message,
59
+ prefix: '',
60
+ }]);
334
61
  };
335
62
 
336
63
  /**
337
- * Text input with animated spinner
64
+ * Text input
338
65
  * @param {string} message - Prompt message
339
66
  * @param {string} [defaultVal=''] - Default value
340
67
  * @returns {Promise<string>}
341
68
  */
342
69
  const textInput = async (message, defaultVal = '') => {
343
- const displayMsg = defaultVal ? `${message} (${defaultVal})` : message;
344
- const value = await animatedPrompt(displayMsg);
345
- return value || defaultVal;
70
+ closeReadline();
71
+ prepareStdin();
72
+
73
+ const { value } = await inquirer.prompt([{
74
+ type: 'input',
75
+ name: 'value',
76
+ message,
77
+ default: defaultVal,
78
+ prefix: '',
79
+ }]);
80
+
81
+ return value;
346
82
  };
347
83
 
348
84
  /**
349
- * Password input (masked) - uses inquirer for masking
85
+ * Password input (masked)
350
86
  * @param {string} message - Prompt message
351
87
  * @returns {Promise<string>}
352
88
  */
@@ -357,7 +93,7 @@ const passwordInput = async (message) => {
357
93
  const { value } = await inquirer.prompt([{
358
94
  type: 'password',
359
95
  name: 'value',
360
- message: `${chalk.yellow('⠋')} ${message}`,
96
+ message,
361
97
  mask: '*',
362
98
  prefix: '',
363
99
  }]);
@@ -366,17 +102,33 @@ const passwordInput = async (message) => {
366
102
  };
367
103
 
368
104
  /**
369
- * Confirm prompt with animated spinner (Y/n)
105
+ * Confirm prompt with Yes/No selection
370
106
  * @param {string} message - Prompt message
371
107
  * @param {boolean} [defaultVal=true] - Default value
372
108
  * @returns {Promise<boolean>}
373
109
  */
374
110
  const confirmPrompt = async (message, defaultVal = true) => {
375
- return animatedConfirm(message, defaultVal);
111
+ closeReadline();
112
+ prepareStdin();
113
+
114
+ const choices = defaultVal
115
+ ? [{ name: 'Yes', value: true }, { name: 'No', value: false }]
116
+ : [{ name: 'No', value: false }, { name: 'Yes', value: true }];
117
+
118
+ const { value } = await inquirer.prompt([{
119
+ type: 'list',
120
+ name: 'value',
121
+ message,
122
+ choices,
123
+ prefix: '',
124
+ loop: false,
125
+ }]);
126
+
127
+ return value;
376
128
  };
377
129
 
378
130
  /**
379
- * Number input with animated spinner and validation
131
+ * Number input with validation
380
132
  * @param {string} message - Prompt message
381
133
  * @param {number} [defaultVal=1] - Default value
382
134
  * @param {number} [min=1] - Minimum value
@@ -384,40 +136,55 @@ const confirmPrompt = async (message, defaultVal = true) => {
384
136
  * @returns {Promise<number>}
385
137
  */
386
138
  const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
387
- const displayMsg = `${message} (${min}-${max}, default: ${defaultVal})`;
388
-
389
- while (true) {
390
- const value = await animatedPrompt(displayMsg);
391
-
392
- if (!value) return defaultVal;
393
-
394
- const num = parseInt(value, 10);
395
- if (isNaN(num)) {
396
- console.log(chalk.red('Please enter a valid number'));
397
- continue;
398
- }
399
- if (num < min || num > max) {
400
- console.log(chalk.red(`Please enter a number between ${min} and ${max}`));
401
- continue;
402
- }
403
- return num;
404
- }
139
+ closeReadline();
140
+ prepareStdin();
141
+
142
+ const { value } = await inquirer.prompt([{
143
+ type: 'input',
144
+ name: 'value',
145
+ message: `${message} (${min}-${max})`,
146
+ default: String(defaultVal),
147
+ prefix: '',
148
+ validate: (v) => {
149
+ const n = parseInt(v, 10);
150
+ if (isNaN(n)) return 'Enter a number';
151
+ if (n < min) return `Min: ${min}`;
152
+ if (n > max) return `Max: ${max}`;
153
+ return true;
154
+ },
155
+ }]);
156
+
157
+ return parseInt(value, 10) || defaultVal;
405
158
  };
406
159
 
407
160
  /**
408
- * Select from options with animated spinner and arrow keys
161
+ * Select from options with arrow keys
409
162
  * @param {string} message - Prompt message
410
163
  * @param {Array<{label: string, value: any, disabled?: boolean}>} options - Options
411
164
  * @returns {Promise<any>}
412
165
  */
413
166
  const selectOption = async (message, options) => {
414
- const choices = options.map(opt => ({
415
- name: opt.label,
416
- value: opt.value,
417
- disabled: opt.disabled || false,
418
- }));
167
+ closeReadline();
168
+ prepareStdin();
169
+
170
+ const choices = options.map(opt => {
171
+ if (opt.disabled) {
172
+ return new inquirer.Separator(opt.label);
173
+ }
174
+ return { name: opt.label, value: opt.value };
175
+ });
419
176
 
420
- return animatedSelect(message, choices);
177
+ const { value } = await inquirer.prompt([{
178
+ type: 'list',
179
+ name: 'value',
180
+ message,
181
+ choices,
182
+ prefix: '',
183
+ loop: false,
184
+ pageSize: 15,
185
+ }]);
186
+
187
+ return value;
421
188
  };
422
189
 
423
190
  module.exports = {