mepcli 0.2.1 → 0.2.5

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/dist/core.js CHANGED
@@ -2,767 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MepCLI = void 0;
4
4
  const ansi_1 = require("./ansi");
5
- /**
6
- * Abstract base class for all prompts.
7
- * Handles common logic like stdin management, raw mode, and cleanup
8
- * to enforce DRY (Don't Repeat Yourself) principles.
9
- */
10
- class Prompt {
11
- constructor(options) {
12
- this.options = options;
13
- this.stdin = process.stdin;
14
- this.stdout = process.stdout;
15
- }
16
- print(text) {
17
- this.stdout.write(text);
18
- }
19
- /**
20
- * Starts the prompt interaction.
21
- * Sets up raw mode and listeners, returning a Promise.
22
- */
23
- run() {
24
- return new Promise((resolve, reject) => {
25
- this._resolve = resolve;
26
- this._reject = reject;
27
- if (typeof this.stdin.setRawMode === 'function') {
28
- this.stdin.setRawMode(true);
29
- }
30
- this.stdin.resume();
31
- this.stdin.setEncoding('utf8');
32
- // Initial render: Default to hidden cursor (good for menus)
33
- // Subclasses like TextPrompt will explicitly show it if needed.
34
- this.print(ansi_1.ANSI.HIDE_CURSOR);
35
- this.render(true);
36
- this._onDataHandler = (buffer) => {
37
- const char = buffer.toString();
38
- // Global Exit Handler (Ctrl+C)
39
- if (char === '\u0003') {
40
- this.cleanup();
41
- this.print(ansi_1.ANSI.SHOW_CURSOR + '\n');
42
- if (this._reject)
43
- this._reject(new Error('User force closed'));
44
- return;
45
- }
46
- this.handleInput(char, buffer);
47
- };
48
- this.stdin.on('data', this._onDataHandler);
49
- });
50
- }
51
- /**
52
- * Cleans up listeners and restores stdin state.
53
- */
54
- cleanup() {
55
- if (this._onDataHandler) {
56
- this.stdin.removeListener('data', this._onDataHandler);
57
- }
58
- if (typeof this.stdin.setRawMode === 'function') {
59
- this.stdin.setRawMode(false);
60
- }
61
- this.stdin.pause();
62
- this.print(ansi_1.ANSI.SHOW_CURSOR);
63
- }
64
- /**
65
- * Submits the final value and resolves the promise.
66
- */
67
- submit(result) {
68
- this.cleanup();
69
- this.print('\n');
70
- if (this._resolve)
71
- this._resolve(result);
72
- }
73
- }
74
- // --- Implementation: Text Prompt ---
75
- class TextPrompt extends Prompt {
76
- constructor(options) {
77
- super(options);
78
- this.errorMsg = '';
79
- this.cursor = 0;
80
- this.hasTyped = false;
81
- this.renderLines = 1;
82
- this.value = options.initial || '';
83
- this.cursor = this.value.length;
84
- }
85
- render(firstRender) {
86
- // TextPrompt needs the cursor visible!
87
- this.print(ansi_1.ANSI.SHOW_CURSOR);
88
- if (!firstRender) {
89
- // Clear previous lines
90
- // Note: renderLines now represents visual wrapped lines
91
- for (let i = 0; i < this.renderLines; i++) {
92
- this.print(ansi_1.ANSI.ERASE_LINE);
93
- if (i < this.renderLines - 1)
94
- this.print(ansi_1.ANSI.UP);
95
- }
96
- this.print(ansi_1.ANSI.CURSOR_LEFT);
97
- }
98
- let output = '';
99
- // 1. Render the Prompt Message
100
- const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
101
- const multilineHint = this.options.multiline ? ` ${MepCLI.theme.muted}(Press Ctrl+D to submit)${ansi_1.ANSI.RESET}` : '';
102
- output += `${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${multilineHint} `;
103
- // 2. Render the Value or Placeholder
104
- let displayValue = '';
105
- if (!this.value && this.options.placeholder && !this.errorMsg && !this.hasTyped) {
106
- displayValue = `${MepCLI.theme.muted}${this.options.placeholder}${ansi_1.ANSI.RESET}`;
107
- }
108
- else {
109
- displayValue = this.options.isPassword ? '*'.repeat(this.value.length) : this.value;
110
- displayValue = `${MepCLI.theme.main}${displayValue}${ansi_1.ANSI.RESET}`;
111
- }
112
- output += displayValue;
113
- // 3. Handle Error Message
114
- if (this.errorMsg) {
115
- output += `\n${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
116
- }
117
- this.print(output);
118
- // 4. Calculate Visual Metrics for Wrapping
119
- const cols = process.stdout.columns || 80;
120
- const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
121
- // Prompt String (visual part before value)
122
- const promptStr = `${icon} ${MepCLI.theme.title}${this.options.message} ${multilineHint} `;
123
- const promptVisualLen = stripAnsi(promptStr).length;
124
- // Value String (visual part)
125
- const rawValue = (!this.value && this.options.placeholder && !this.errorMsg && !this.hasTyped)
126
- ? this.options.placeholder || ''
127
- : (this.options.isPassword ? '*'.repeat(this.value.length) : this.value);
128
- // Error String (visual part)
129
- const errorVisualLines = this.errorMsg ? Math.ceil((3 + this.errorMsg.length) / cols) : 0;
130
- // Calculate Total Lines and Cursor Position
131
- // We simulate printing the prompt + value + error
132
- let currentVisualLine = 0;
133
- let currentCol = 0;
134
- // State tracking for cursor
135
- let cursorRow = 0;
136
- let cursorCol = 0;
137
- // Add Prompt
138
- currentCol += promptVisualLen;
139
- while (currentCol >= cols) {
140
- currentVisualLine++;
141
- currentCol -= cols;
142
- }
143
- // Add Value (Character by character to handle wrapping and cursor tracking accurately)
144
- // Note: This doesn't handle multi-width chars perfectly, but handles wrapping better than before
145
- const valueLen = rawValue.length;
146
- // If placeholder, we treat it as value for render height, but cursor is at 0
147
- const isPlaceholder = (!this.value && this.options.placeholder && !this.errorMsg && !this.hasTyped);
148
- for (let i = 0; i < valueLen; i++) {
149
- // Check if we are at cursor position
150
- if (!isPlaceholder && i === this.cursor) {
151
- cursorRow = currentVisualLine;
152
- cursorCol = currentCol;
153
- }
154
- const char = rawValue[i];
155
- if (char === '\n') {
156
- currentVisualLine++;
157
- currentCol = 0;
158
- }
159
- else {
160
- currentCol++;
161
- if (currentCol >= cols) {
162
- currentVisualLine++;
163
- currentCol = 0;
164
- }
165
- }
166
- }
167
- // If cursor is at the very end
168
- if (!isPlaceholder && this.cursor === valueLen) {
169
- cursorRow = currentVisualLine;
170
- cursorCol = currentCol;
171
- }
172
- // If placeholder, cursor is at start of value
173
- if (isPlaceholder) {
174
- // Re-calc cursor position as if it's at index 0 of value
175
- // Which is effectively where prompt ends
176
- // We already updated currentCol/Line for prompt above, but loop continued for placeholder
177
- // So we need to recalculate or store prompt end state
178
- // Let's just use prompt end state:
179
- let pCol = promptVisualLen;
180
- let pRow = 0;
181
- while (pCol >= cols) {
182
- pRow++;
183
- pCol -= cols;
184
- }
185
- cursorRow = pRow;
186
- cursorCol = pCol;
187
- }
188
- // Final height
189
- // If we are at col 0 of a new line (e.g. just wrapped or \n), we count that line
190
- // currentVisualLine is 0-indexed index of the line we are on.
191
- // Total lines = currentVisualLine + 1 + errorLines
192
- // Special case: if input ends with \n, we are on a new empty line
193
- const totalValueRows = currentVisualLine + 1;
194
- this.renderLines = totalValueRows + errorVisualLines;
195
- // 5. Position Cursor Logic
196
- // We are currently at the end of output.
197
- // End row relative to start: this.renderLines - 1
198
- const endRow = this.renderLines - 1;
199
- // Move up to cursor row
200
- const linesUp = endRow - cursorRow;
201
- if (linesUp > 0) {
202
- this.print(`\x1b[${linesUp}A`);
203
- }
204
- // Move to cursor col
205
- this.print(ansi_1.ANSI.CURSOR_LEFT); // Go to col 0
206
- if (cursorCol > 0) {
207
- this.print(`\x1b[${cursorCol}C`);
208
- }
209
- }
210
- handleInput(char) {
211
- // Enter
212
- if (char === '\r' || char === '\n') {
213
- if (this.options.multiline) {
214
- this.value = this.value.slice(0, this.cursor) + '\n' + this.value.slice(this.cursor);
215
- this.cursor++;
216
- this.render(false);
217
- return;
218
- }
219
- this.validateAndSubmit();
220
- return;
221
- }
222
- // Ctrl+D (EOF) or Ctrl+S for Submit in Multiline
223
- if (this.options.multiline && (char === '\u0004' || char === '\u0013')) {
224
- this.validateAndSubmit();
225
- return;
226
- }
227
- // Backspace
228
- if (char === '\u0008' || char === '\x7f') {
229
- this.hasTyped = true;
230
- if (this.cursor > 0) {
231
- this.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor);
232
- this.cursor--;
233
- this.errorMsg = '';
234
- this.render(false);
235
- }
236
- return;
237
- }
238
- // Arrow Left
239
- if (char === '\u001b[D') {
240
- if (this.cursor > 0) {
241
- this.cursor--;
242
- this.render(false);
243
- }
244
- return;
245
- }
246
- // Arrow Right
247
- if (char === '\u001b[C') {
248
- if (this.cursor < this.value.length) {
249
- this.cursor++;
250
- this.render(false);
251
- }
252
- return;
253
- }
254
- // Delete key
255
- if (char === '\u001b[3~') {
256
- this.hasTyped = true;
257
- if (this.cursor < this.value.length) {
258
- this.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1);
259
- this.errorMsg = '';
260
- this.render(false);
261
- }
262
- return;
263
- }
264
- // Regular Typing & Paste
265
- if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
266
- this.hasTyped = true;
267
- this.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);
268
- this.cursor += char.length;
269
- this.errorMsg = '';
270
- this.render(false);
271
- }
272
- }
273
- validateAndSubmit() {
274
- if (this.options.validate) {
275
- const result = this.options.validate(this.value);
276
- // Handle Promise validation
277
- if (result instanceof Promise) {
278
- // Show loading state
279
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.main}Validating...${ansi_1.ANSI.RESET}`);
280
- this.print(ansi_1.ANSI.UP);
281
- result.then(valid => {
282
- // Clear loading message
283
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}`);
284
- this.print(ansi_1.ANSI.UP);
285
- if (typeof valid === 'string' && valid.length > 0) {
286
- this.errorMsg = valid;
287
- this.render(false);
288
- }
289
- else if (valid === false) {
290
- this.errorMsg = 'Invalid input';
291
- this.render(false);
292
- }
293
- else {
294
- if (this.errorMsg) {
295
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
296
- }
297
- this.submit(this.value);
298
- }
299
- }).catch(err => {
300
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}`);
301
- this.print(ansi_1.ANSI.UP);
302
- this.errorMsg = err.message || 'Validation failed';
303
- this.render(false);
304
- });
305
- return;
306
- }
307
- // Handle Sync validation
308
- if (typeof result === 'string' && result.length > 0) {
309
- this.errorMsg = result;
310
- this.render(false);
311
- return;
312
- }
313
- if (result === false) {
314
- this.errorMsg = 'Invalid input';
315
- this.render(false);
316
- return;
317
- }
318
- }
319
- if (this.errorMsg) {
320
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
321
- }
322
- this.submit(this.value);
323
- }
324
- }
325
- // --- Implementation: Select Prompt ---
326
- class SelectPrompt extends Prompt {
327
- constructor(options) {
328
- super(options);
329
- this.selectedIndex = 0;
330
- this.searchBuffer = '';
331
- this.scrollTop = 0;
332
- this.pageSize = 7;
333
- // Custom render to handle variable height clearing
334
- this.lastRenderHeight = 0;
335
- // Find first non-separator index
336
- this.selectedIndex = this.findNextSelectableIndex(-1, 1);
337
- }
338
- isSeparator(item) {
339
- return item && item.separator === true;
340
- }
341
- findNextSelectableIndex(currentIndex, direction) {
342
- let nextIndex = currentIndex + direction;
343
- const choices = this.getFilteredChoices();
344
- // Loop around logic
345
- if (nextIndex < 0)
346
- nextIndex = choices.length - 1;
347
- if (nextIndex >= choices.length)
348
- nextIndex = 0;
349
- if (choices.length === 0)
350
- return 0;
351
- // Safety check to prevent infinite loop if all are separators (shouldn't happen in practice)
352
- let count = 0;
353
- while (this.isSeparator(choices[nextIndex]) && count < choices.length) {
354
- nextIndex += direction;
355
- if (nextIndex < 0)
356
- nextIndex = choices.length - 1;
357
- if (nextIndex >= choices.length)
358
- nextIndex = 0;
359
- count++;
360
- }
361
- return nextIndex;
362
- }
363
- getFilteredChoices() {
364
- if (!this.searchBuffer)
365
- return this.options.choices;
366
- return this.options.choices.filter(c => {
367
- if (this.isSeparator(c))
368
- return false; // Hide separators when searching
369
- return c.title.toLowerCase().includes(this.searchBuffer.toLowerCase());
370
- });
371
- }
372
- renderWrapper(firstRender) {
373
- if (!firstRender && this.lastRenderHeight > 0) {
374
- this.print(`\x1b[${this.lastRenderHeight}A`);
375
- }
376
- let output = '';
377
- const choices = this.getFilteredChoices();
378
- // Adjust Scroll Top
379
- if (this.selectedIndex < this.scrollTop) {
380
- this.scrollTop = this.selectedIndex;
381
- }
382
- else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
383
- this.scrollTop = this.selectedIndex - this.pageSize + 1;
384
- }
385
- // Handle Filtering Edge Case: if list shrinks, scrollTop might be too high
386
- if (this.scrollTop > choices.length - 1) {
387
- this.scrollTop = Math.max(0, choices.length - this.pageSize);
388
- }
389
- // Header
390
- const searchStr = this.searchBuffer ? ` ${MepCLI.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
391
- output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
392
- if (choices.length === 0) {
393
- output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT} ${MepCLI.theme.muted}No results found${ansi_1.ANSI.RESET}\n`;
394
- }
395
- else {
396
- const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
397
- visibleChoices.forEach((choice, index) => {
398
- const actualIndex = this.scrollTop + index;
399
- output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`;
400
- if (this.isSeparator(choice)) {
401
- output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}\n`;
402
- }
403
- else {
404
- if (actualIndex === this.selectedIndex) {
405
- output += `${MepCLI.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}\n`;
406
- }
407
- else {
408
- output += ` ${choice.title}\n`;
409
- }
410
- }
411
- });
412
- }
413
- this.print(output);
414
- // Clear remaining lines if list shrunk
415
- const visibleCount = Math.min(choices.length, this.pageSize);
416
- const currentHeight = visibleCount + 1 + (choices.length === 0 ? 1 : 0);
417
- const linesToClear = this.lastRenderHeight - currentHeight;
418
- if (linesToClear > 0) {
419
- for (let i = 0; i < linesToClear; i++) {
420
- this.print(`${ansi_1.ANSI.ERASE_LINE}\n`);
421
- }
422
- this.print(`\x1b[${linesToClear}A`); // Move back up
423
- }
424
- this.lastRenderHeight = currentHeight;
425
- }
426
- render(firstRender) {
427
- this.print(ansi_1.ANSI.HIDE_CURSOR);
428
- this.renderWrapper(firstRender);
429
- }
430
- handleInput(char) {
431
- const choices = this.getFilteredChoices();
432
- if (char === '\r' || char === '\n') {
433
- if (choices.length === 0) {
434
- this.searchBuffer = '';
435
- this.selectedIndex = this.findNextSelectableIndex(-1, 1);
436
- this.render(false);
437
- return;
438
- }
439
- if (this.isSeparator(choices[this.selectedIndex]))
440
- return;
441
- this.cleanup();
442
- this.print(ansi_1.ANSI.SHOW_CURSOR);
443
- if (this._resolve)
444
- this._resolve(choices[this.selectedIndex].value);
445
- return;
446
- }
447
- if (char === '\u001b[A') { // Up
448
- if (choices.length > 0) {
449
- this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, -1);
450
- this.render(false);
451
- }
452
- return;
453
- }
454
- if (char === '\u001b[B') { // Down
455
- if (choices.length > 0) {
456
- this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, 1);
457
- this.render(false);
458
- }
459
- return;
460
- }
461
- // Backspace
462
- if (char === '\u0008' || char === '\x7f') {
463
- if (this.searchBuffer.length > 0) {
464
- this.searchBuffer = this.searchBuffer.slice(0, -1);
465
- this.selectedIndex = 0; // Reset selection
466
- this.selectedIndex = this.findNextSelectableIndex(-1, 1);
467
- this.render(false);
468
- }
469
- return;
470
- }
471
- // Typing
472
- if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
473
- this.searchBuffer += char;
474
- this.selectedIndex = 0; // Reset selection
475
- this.selectedIndex = this.findNextSelectableIndex(-1, 1);
476
- this.render(false);
477
- }
478
- }
479
- }
480
- // --- Implementation: Checkbox Prompt ---
481
- class CheckboxPrompt extends Prompt {
482
- constructor(options) {
483
- super(options);
484
- this.selectedIndex = 0;
485
- this.errorMsg = '';
486
- this.checkedState = options.choices.map(c => !!c.selected);
487
- }
488
- render(firstRender) {
489
- // Ensure cursor is HIDDEN for menus
490
- this.print(ansi_1.ANSI.HIDE_CURSOR);
491
- if (!firstRender) {
492
- const extraLines = this.errorMsg ? 1 : 0;
493
- this.print(`\x1b[${this.options.choices.length + 1 + extraLines}A`);
494
- }
495
- this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
496
- const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
497
- this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}(Press <space> to select, <enter> to confirm)${ansi_1.ANSI.RESET}\n`);
498
- this.options.choices.forEach((choice, index) => {
499
- this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
500
- const cursor = index === this.selectedIndex ? `${MepCLI.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
501
- const isChecked = this.checkedState[index];
502
- const checkbox = isChecked
503
- ? `${MepCLI.theme.success}◉${ansi_1.ANSI.RESET}`
504
- : `${MepCLI.theme.muted}◯${ansi_1.ANSI.RESET}`;
505
- const title = index === this.selectedIndex
506
- ? `${MepCLI.theme.main}${choice.title}${ansi_1.ANSI.RESET}`
507
- : choice.title;
508
- this.print(`${cursor} ${checkbox} ${title}\n`);
509
- });
510
- if (this.errorMsg) {
511
- this.print(`${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
512
- }
513
- else if (!firstRender) {
514
- this.print(`${ansi_1.ANSI.ERASE_LINE}`);
515
- }
516
- }
517
- handleInput(char) {
518
- if (char === '\r' || char === '\n') {
519
- const selectedCount = this.checkedState.filter(Boolean).length;
520
- const { min = 0, max } = this.options;
521
- if (selectedCount < min) {
522
- this.errorMsg = `You must select at least ${min} options.`;
523
- this.render(false);
524
- return;
525
- }
526
- if (max && selectedCount > max) {
527
- this.errorMsg = `You can only select up to ${max} options.`;
528
- this.render(false);
529
- return;
530
- }
531
- this.cleanup();
532
- this.print(ansi_1.ANSI.SHOW_CURSOR + '\n');
533
- const results = this.options.choices
534
- .filter((_, i) => this.checkedState[i])
535
- .map(c => c.value);
536
- if (this._resolve)
537
- this._resolve(results);
538
- return;
539
- }
540
- if (char === ' ') {
541
- const currentChecked = this.checkedState[this.selectedIndex];
542
- const selectedCount = this.checkedState.filter(Boolean).length;
543
- const { max } = this.options;
544
- if (!currentChecked && max && selectedCount >= max) {
545
- this.errorMsg = `Max ${max} selections allowed.`;
546
- }
547
- else {
548
- this.checkedState[this.selectedIndex] = !currentChecked;
549
- this.errorMsg = '';
550
- }
551
- this.render(false);
552
- }
553
- if (char === '\u001b[A') { // Up
554
- this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.choices.length - 1;
555
- this.errorMsg = '';
556
- this.render(false);
557
- }
558
- if (char === '\u001b[B') { // Down
559
- this.selectedIndex = this.selectedIndex < this.options.choices.length - 1 ? this.selectedIndex + 1 : 0;
560
- this.errorMsg = '';
561
- this.render(false);
562
- }
563
- }
564
- }
565
- // --- Implementation: Confirm Prompt ---
566
- class ConfirmPrompt extends Prompt {
567
- constructor(options) {
568
- super(options);
569
- this.value = options.initial ?? true;
570
- }
571
- render(firstRender) {
572
- // Hide cursor for confirm, user just hits Y/N or Enter
573
- this.print(ansi_1.ANSI.HIDE_CURSOR);
574
- if (!firstRender) {
575
- this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
576
- }
577
- const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
578
- this.print(`${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}(${hint})${ansi_1.ANSI.RESET} `);
579
- const text = this.value ? 'Yes' : 'No';
580
- this.print(`${MepCLI.theme.main}${text}${ansi_1.ANSI.RESET}\x1b[${text.length}D`);
581
- }
582
- handleInput(char) {
583
- const c = char.toLowerCase();
584
- if (c === '\r' || c === '\n') {
585
- this.submit(this.value);
586
- return;
587
- }
588
- if (c === 'y') {
589
- this.value = true;
590
- this.render(false);
591
- }
592
- if (c === 'n') {
593
- this.value = false;
594
- this.render(false);
595
- }
596
- }
597
- }
598
- // --- Implementation: Toggle Prompt ---
599
- class TogglePrompt extends Prompt {
600
- constructor(options) {
601
- super(options);
602
- this.value = options.initial ?? false;
603
- }
604
- render(firstRender) {
605
- this.print(ansi_1.ANSI.HIDE_CURSOR);
606
- if (!firstRender) {
607
- this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
608
- }
609
- const activeText = this.options.activeText || 'ON';
610
- const inactiveText = this.options.inactiveText || 'OFF';
611
- let toggleDisplay = '';
612
- if (this.value) {
613
- toggleDisplay = `${MepCLI.theme.main}[${ansi_1.ANSI.BOLD}${activeText}${ansi_1.ANSI.RESET}${MepCLI.theme.main}]${ansi_1.ANSI.RESET} ${MepCLI.theme.muted}${inactiveText}${ansi_1.ANSI.RESET}`;
614
- }
615
- else {
616
- toggleDisplay = `${MepCLI.theme.muted}${activeText}${ansi_1.ANSI.RESET} ${MepCLI.theme.main}[${ansi_1.ANSI.BOLD}${inactiveText}${ansi_1.ANSI.RESET}${MepCLI.theme.main}]${ansi_1.ANSI.RESET}`;
617
- }
618
- this.print(`${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${toggleDisplay}`);
619
- this.print(`\x1b[${toggleDisplay.length}D`); // Move back is not really needed as we hide cursor, but kept for consistency
620
- }
621
- handleInput(char) {
622
- if (char === '\r' || char === '\n') {
623
- this.submit(this.value);
624
- return;
625
- }
626
- if (char === '\u001b[D' || char === '\u001b[C' || char === 'h' || char === 'l') { // Left/Right
627
- this.value = !this.value;
628
- this.render(false);
629
- }
630
- if (char === ' ') {
631
- this.value = !this.value;
632
- this.render(false);
633
- }
634
- }
635
- }
636
- // --- Implementation: Number Prompt ---
637
- class NumberPrompt extends Prompt {
638
- constructor(options) {
639
- super(options);
640
- this.cursor = 0;
641
- this.errorMsg = '';
642
- this.value = options.initial ?? 0;
643
- this.stringValue = this.value.toString();
644
- this.cursor = this.stringValue.length;
645
- }
646
- render(firstRender) {
647
- this.print(ansi_1.ANSI.SHOW_CURSOR);
648
- if (!firstRender) {
649
- this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
650
- if (this.errorMsg) {
651
- this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
652
- }
653
- }
654
- // 1. Render the Prompt Message
655
- this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
656
- const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
657
- this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
658
- // 2. Render the Value
659
- this.print(`${MepCLI.theme.main}${this.stringValue}${ansi_1.ANSI.RESET}`);
660
- // 3. Handle Error Message
661
- if (this.errorMsg) {
662
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
663
- this.print(ansi_1.ANSI.UP);
664
- const promptLen = this.options.message.length + 3;
665
- const valLen = this.stringValue.length;
666
- this.print(`\x1b[1000D\x1b[${promptLen + valLen}C`);
667
- }
668
- // 4. Position Cursor
669
- const diff = this.stringValue.length - this.cursor;
670
- if (diff > 0) {
671
- this.print(`\x1b[${diff}D`);
672
- }
673
- }
674
- handleInput(char) {
675
- // Enter
676
- if (char === '\r' || char === '\n') {
677
- const num = parseFloat(this.stringValue);
678
- if (isNaN(num)) {
679
- this.errorMsg = 'Please enter a valid number.';
680
- this.render(false);
681
- return;
682
- }
683
- if (this.options.min !== undefined && num < this.options.min) {
684
- this.errorMsg = `Minimum value is ${this.options.min}`;
685
- this.render(false);
686
- return;
687
- }
688
- if (this.options.max !== undefined && num > this.options.max) {
689
- this.errorMsg = `Maximum value is ${this.options.max}`;
690
- this.render(false);
691
- return;
692
- }
693
- if (this.errorMsg) {
694
- this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
695
- }
696
- this.submit(num);
697
- return;
698
- }
699
- // Up Arrow (Increment)
700
- if (char === '\u001b[A') {
701
- let num = parseFloat(this.stringValue) || 0;
702
- num += (this.options.step ?? 1);
703
- if (this.options.max !== undefined && num > this.options.max)
704
- num = this.options.max;
705
- this.stringValue = num.toString();
706
- this.cursor = this.stringValue.length;
707
- this.errorMsg = '';
708
- this.render(false);
709
- return;
710
- }
711
- // Down Arrow (Decrement)
712
- if (char === '\u001b[B') {
713
- let num = parseFloat(this.stringValue) || 0;
714
- num -= (this.options.step ?? 1);
715
- if (this.options.min !== undefined && num < this.options.min)
716
- num = this.options.min;
717
- this.stringValue = num.toString();
718
- this.cursor = this.stringValue.length;
719
- this.errorMsg = '';
720
- this.render(false);
721
- return;
722
- }
723
- // Backspace
724
- if (char === '\u0008' || char === '\x7f') {
725
- if (this.cursor > 0) {
726
- this.stringValue = this.stringValue.slice(0, this.cursor - 1) + this.stringValue.slice(this.cursor);
727
- this.cursor--;
728
- this.errorMsg = '';
729
- this.render(false);
730
- }
731
- return;
732
- }
733
- // Arrow Left
734
- if (char === '\u001b[D') {
735
- if (this.cursor > 0) {
736
- this.cursor--;
737
- this.render(false);
738
- }
739
- return;
740
- }
741
- // Arrow Right
742
- if (char === '\u001b[C') {
743
- if (this.cursor < this.stringValue.length) {
744
- this.cursor++;
745
- this.render(false);
746
- }
747
- return;
748
- }
749
- // Numeric Input (and . and -)
750
- // Simple paste support for numbers is also good
751
- if (/^[0-9.\-]+$/.test(char)) {
752
- // Basic validation for pasted content
753
- if (char.includes('-') && (this.cursor !== 0 || this.stringValue.includes('-') || char.lastIndexOf('-') > 0)) {
754
- // If complex paste fails simple checks, ignore or let user correct
755
- // For now, strict check on single char logic is preserved if we want,
756
- // but let's allow pasting valid number strings
757
- }
758
- // Allow if it looks like a number part
759
- this.stringValue = this.stringValue.slice(0, this.cursor) + char + this.stringValue.slice(this.cursor);
760
- this.cursor += char.length;
761
- this.errorMsg = '';
762
- this.render(false);
763
- }
764
- }
765
- }
5
+ const theme_1 = require("./theme");
6
+ const text_1 = require("./prompts/text");
7
+ const select_1 = require("./prompts/select");
8
+ const checkbox_1 = require("./prompts/checkbox");
9
+ const confirm_1 = require("./prompts/confirm");
10
+ const toggle_1 = require("./prompts/toggle");
11
+ const number_1 = require("./prompts/number");
12
+ const list_1 = require("./prompts/list");
13
+ const slider_1 = require("./prompts/slider");
14
+ const date_1 = require("./prompts/date");
15
+ const file_1 = require("./prompts/file");
16
+ const multi_select_1 = require("./prompts/multi-select");
766
17
  /**
767
18
  * Public Facade for MepCLI
768
19
  */
@@ -793,32 +44,41 @@ class MepCLI {
793
44
  }
794
45
  }
795
46
  static text(options) {
796
- return new TextPrompt(options).run();
47
+ return new text_1.TextPrompt(options).run();
797
48
  }
798
49
  static select(options) {
799
- return new SelectPrompt(options).run();
50
+ return new select_1.SelectPrompt(options).run();
800
51
  }
801
52
  static checkbox(options) {
802
- return new CheckboxPrompt(options).run();
53
+ return new checkbox_1.CheckboxPrompt(options).run();
803
54
  }
804
55
  static confirm(options) {
805
- return new ConfirmPrompt(options).run();
56
+ return new confirm_1.ConfirmPrompt(options).run();
806
57
  }
807
58
  static password(options) {
808
- return new TextPrompt({ ...options, isPassword: true }).run();
59
+ return new text_1.TextPrompt({ ...options, isPassword: true }).run();
809
60
  }
810
61
  static number(options) {
811
- return new NumberPrompt(options).run();
62
+ return new number_1.NumberPrompt(options).run();
812
63
  }
813
64
  static toggle(options) {
814
- return new TogglePrompt(options).run();
65
+ return new toggle_1.TogglePrompt(options).run();
66
+ }
67
+ static list(options) {
68
+ return new list_1.ListPrompt(options).run();
69
+ }
70
+ static slider(options) {
71
+ return new slider_1.SliderPrompt(options).run();
72
+ }
73
+ static date(options) {
74
+ return new date_1.DatePrompt(options).run();
75
+ }
76
+ static file(options) {
77
+ return new file_1.FilePrompt(options).run();
78
+ }
79
+ static multiSelect(options) {
80
+ return new multi_select_1.MultiSelectPrompt(options).run();
815
81
  }
816
82
  }
817
83
  exports.MepCLI = MepCLI;
818
- MepCLI.theme = {
819
- main: ansi_1.ANSI.FG_CYAN,
820
- success: ansi_1.ANSI.FG_GREEN,
821
- error: ansi_1.ANSI.FG_RED,
822
- muted: ansi_1.ANSI.FG_GRAY,
823
- title: ansi_1.ANSI.RESET
824
- };
84
+ MepCLI.theme = theme_1.theme;