mepcli 0.2.0 → 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/README.md +61 -17
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.js +2 -0
- package/dist/base.d.ts +44 -0
- package/dist/base.js +87 -0
- package/dist/core.d.ts +6 -1
- package/dist/core.js +35 -619
- package/dist/prompts/checkbox.d.ts +10 -0
- package/dist/prompts/checkbox.js +92 -0
- package/dist/prompts/confirm.d.ts +7 -0
- package/dist/prompts/confirm.js +40 -0
- package/dist/prompts/date.d.ts +10 -0
- package/dist/prompts/date.js +157 -0
- package/dist/prompts/file.d.ts +13 -0
- package/dist/prompts/file.js +194 -0
- package/dist/prompts/list.d.ts +9 -0
- package/dist/prompts/list.js +83 -0
- package/dist/prompts/multi-select.d.ts +14 -0
- package/dist/prompts/multi-select.js +149 -0
- package/dist/prompts/number.d.ts +10 -0
- package/dist/prompts/number.js +137 -0
- package/dist/prompts/select.d.ts +16 -0
- package/dist/prompts/select.js +162 -0
- package/dist/prompts/slider.d.ts +7 -0
- package/dist/prompts/slider.js +48 -0
- package/dist/prompts/text.d.ts +12 -0
- package/dist/prompts/text.js +245 -0
- package/dist/prompts/toggle.d.ts +7 -0
- package/dist/prompts/toggle.js +45 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +11 -0
- package/dist/types.d.ts +26 -1
- package/example.ts +95 -23
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -2,611 +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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.value = options.initial || '';
|
|
82
|
-
this.cursor = this.value.length;
|
|
83
|
-
}
|
|
84
|
-
render(firstRender) {
|
|
85
|
-
// TextPrompt needs the cursor visible!
|
|
86
|
-
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
87
|
-
if (!firstRender) {
|
|
88
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
89
|
-
if (this.errorMsg) {
|
|
90
|
-
this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// 1. Render the Prompt Message
|
|
94
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
95
|
-
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
96
|
-
this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
97
|
-
// 2. Render the Value or Placeholder
|
|
98
|
-
if (!this.value && this.options.placeholder && !this.errorMsg && !this.hasTyped) {
|
|
99
|
-
this.print(`${MepCLI.theme.muted}${this.options.placeholder}${ansi_1.ANSI.RESET}`);
|
|
100
|
-
// Move cursor back to start so typing overwrites placeholder visually
|
|
101
|
-
this.print(`\x1b[${this.options.placeholder.length}D`);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
const displayValue = this.options.isPassword ? '*'.repeat(this.value.length) : this.value;
|
|
105
|
-
this.print(`${MepCLI.theme.main}${displayValue}${ansi_1.ANSI.RESET}`);
|
|
106
|
-
}
|
|
107
|
-
// 3. Handle Error Message
|
|
108
|
-
if (this.errorMsg) {
|
|
109
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
110
|
-
this.print(ansi_1.ANSI.UP); // Go back to input line
|
|
111
|
-
// Re-calculate position to end of input
|
|
112
|
-
const promptLen = this.options.message.length + 3; // Icon + 2 spaces
|
|
113
|
-
const valLen = this.value.length;
|
|
114
|
-
// Move to absolute start of line, then move right to end of string
|
|
115
|
-
this.print(`\x1b[1000D\x1b[${promptLen + valLen}C`);
|
|
116
|
-
}
|
|
117
|
-
// 4. Position Cursor Logic
|
|
118
|
-
// At this point, the physical cursor is at the END of the value string.
|
|
119
|
-
// We need to move it LEFT by (length - cursor_index)
|
|
120
|
-
const diff = this.value.length - this.cursor;
|
|
121
|
-
if (diff > 0) {
|
|
122
|
-
this.print(`\x1b[${diff}D`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
handleInput(char) {
|
|
126
|
-
// Enter
|
|
127
|
-
if (char === '\r' || char === '\n') {
|
|
128
|
-
if (this.options.validate) {
|
|
129
|
-
const validationResult = this.options.validate(this.value);
|
|
130
|
-
if (typeof validationResult === 'string' && validationResult.length > 0) {
|
|
131
|
-
this.errorMsg = validationResult;
|
|
132
|
-
this.render(false);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (this.errorMsg) {
|
|
137
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
|
|
138
|
-
}
|
|
139
|
-
this.submit(this.value);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// Backspace
|
|
143
|
-
if (char === '\u0008' || char === '\x7f') {
|
|
144
|
-
this.hasTyped = true;
|
|
145
|
-
if (this.cursor > 0) {
|
|
146
|
-
this.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor);
|
|
147
|
-
this.cursor--;
|
|
148
|
-
this.errorMsg = '';
|
|
149
|
-
this.render(false);
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
// Arrow Left
|
|
154
|
-
if (char === '\u001b[D') {
|
|
155
|
-
if (this.cursor > 0) {
|
|
156
|
-
this.cursor--;
|
|
157
|
-
this.render(false);
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
// Arrow Right
|
|
162
|
-
if (char === '\u001b[C') {
|
|
163
|
-
if (this.cursor < this.value.length) {
|
|
164
|
-
this.cursor++;
|
|
165
|
-
this.render(false);
|
|
166
|
-
}
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
// Delete key
|
|
170
|
-
if (char === '\u001b[3~') {
|
|
171
|
-
this.hasTyped = true;
|
|
172
|
-
if (this.cursor < this.value.length) {
|
|
173
|
-
this.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1);
|
|
174
|
-
this.errorMsg = '';
|
|
175
|
-
this.render(false);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
// Regular Typing
|
|
180
|
-
if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
|
|
181
|
-
this.hasTyped = true;
|
|
182
|
-
this.value = this.value.slice(0, this.cursor) + char + this.value.slice(this.cursor);
|
|
183
|
-
this.cursor++;
|
|
184
|
-
this.errorMsg = '';
|
|
185
|
-
this.render(false);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// --- Implementation: Select Prompt ---
|
|
190
|
-
class SelectPrompt extends Prompt {
|
|
191
|
-
constructor(options) {
|
|
192
|
-
super(options);
|
|
193
|
-
this.selectedIndex = 0;
|
|
194
|
-
this.searchBuffer = '';
|
|
195
|
-
// Custom render to handle variable height clearing
|
|
196
|
-
this.lastRenderHeight = 0;
|
|
197
|
-
// Find first non-separator index
|
|
198
|
-
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
199
|
-
}
|
|
200
|
-
isSeparator(item) {
|
|
201
|
-
return item && item.separator === true;
|
|
202
|
-
}
|
|
203
|
-
findNextSelectableIndex(currentIndex, direction) {
|
|
204
|
-
let nextIndex = currentIndex + direction;
|
|
205
|
-
const choices = this.getFilteredChoices();
|
|
206
|
-
// Loop around logic
|
|
207
|
-
if (nextIndex < 0)
|
|
208
|
-
nextIndex = choices.length - 1;
|
|
209
|
-
if (nextIndex >= choices.length)
|
|
210
|
-
nextIndex = 0;
|
|
211
|
-
if (choices.length === 0)
|
|
212
|
-
return 0;
|
|
213
|
-
// Safety check to prevent infinite loop if all are separators (shouldn't happen in practice)
|
|
214
|
-
let count = 0;
|
|
215
|
-
while (this.isSeparator(choices[nextIndex]) && count < choices.length) {
|
|
216
|
-
nextIndex += direction;
|
|
217
|
-
if (nextIndex < 0)
|
|
218
|
-
nextIndex = choices.length - 1;
|
|
219
|
-
if (nextIndex >= choices.length)
|
|
220
|
-
nextIndex = 0;
|
|
221
|
-
count++;
|
|
222
|
-
}
|
|
223
|
-
return nextIndex;
|
|
224
|
-
}
|
|
225
|
-
getFilteredChoices() {
|
|
226
|
-
if (!this.searchBuffer)
|
|
227
|
-
return this.options.choices;
|
|
228
|
-
return this.options.choices.filter(c => {
|
|
229
|
-
if (this.isSeparator(c))
|
|
230
|
-
return false; // Hide separators when searching
|
|
231
|
-
return c.title.toLowerCase().includes(this.searchBuffer.toLowerCase());
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
renderWrapper(firstRender) {
|
|
235
|
-
if (!firstRender && this.lastRenderHeight > 0) {
|
|
236
|
-
this.print(`\x1b[${this.lastRenderHeight}A`);
|
|
237
|
-
}
|
|
238
|
-
let output = '';
|
|
239
|
-
const choices = this.getFilteredChoices();
|
|
240
|
-
// Header
|
|
241
|
-
const searchStr = this.searchBuffer ? ` ${MepCLI.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
242
|
-
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`;
|
|
243
|
-
if (choices.length === 0) {
|
|
244
|
-
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT} ${MepCLI.theme.muted}No results found${ansi_1.ANSI.RESET}\n`;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
choices.forEach((choice, index) => {
|
|
248
|
-
output += `${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`;
|
|
249
|
-
if (this.isSeparator(choice)) {
|
|
250
|
-
output += ` ${ansi_1.ANSI.DIM}${choice.text || '────────'}${ansi_1.ANSI.RESET}\n`;
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
if (index === this.selectedIndex) {
|
|
254
|
-
output += `${MepCLI.theme.main}❯ ${choice.title}${ansi_1.ANSI.RESET}\n`;
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
output += ` ${choice.title}\n`;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
this.print(output);
|
|
263
|
-
// Clear remaining lines if list shrunk
|
|
264
|
-
const currentHeight = choices.length + 1 + (choices.length === 0 ? 1 : 0);
|
|
265
|
-
const linesToClear = this.lastRenderHeight - currentHeight;
|
|
266
|
-
if (linesToClear > 0) {
|
|
267
|
-
for (let i = 0; i < linesToClear; i++) {
|
|
268
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}\n`);
|
|
269
|
-
}
|
|
270
|
-
this.print(`\x1b[${linesToClear}A`); // Move back up
|
|
271
|
-
}
|
|
272
|
-
this.lastRenderHeight = currentHeight;
|
|
273
|
-
}
|
|
274
|
-
render(firstRender) {
|
|
275
|
-
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
276
|
-
this.renderWrapper(firstRender);
|
|
277
|
-
}
|
|
278
|
-
handleInput(char) {
|
|
279
|
-
const choices = this.getFilteredChoices();
|
|
280
|
-
if (char === '\r' || char === '\n') {
|
|
281
|
-
if (choices.length === 0) {
|
|
282
|
-
this.searchBuffer = '';
|
|
283
|
-
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
284
|
-
this.render(false);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (this.isSeparator(choices[this.selectedIndex]))
|
|
288
|
-
return;
|
|
289
|
-
this.cleanup();
|
|
290
|
-
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
291
|
-
if (this._resolve)
|
|
292
|
-
this._resolve(choices[this.selectedIndex].value);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (char === '\u001b[A') { // Up
|
|
296
|
-
if (choices.length > 0) {
|
|
297
|
-
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, -1);
|
|
298
|
-
this.render(false);
|
|
299
|
-
}
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (char === '\u001b[B') { // Down
|
|
303
|
-
if (choices.length > 0) {
|
|
304
|
-
this.selectedIndex = this.findNextSelectableIndex(this.selectedIndex, 1);
|
|
305
|
-
this.render(false);
|
|
306
|
-
}
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
// Backspace
|
|
310
|
-
if (char === '\u0008' || char === '\x7f') {
|
|
311
|
-
if (this.searchBuffer.length > 0) {
|
|
312
|
-
this.searchBuffer = this.searchBuffer.slice(0, -1);
|
|
313
|
-
this.selectedIndex = 0; // Reset selection
|
|
314
|
-
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
315
|
-
this.render(false);
|
|
316
|
-
}
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// Typing
|
|
320
|
-
if (char.length === 1 && !/^[\x00-\x1F]/.test(char)) {
|
|
321
|
-
this.searchBuffer += char;
|
|
322
|
-
this.selectedIndex = 0; // Reset selection
|
|
323
|
-
this.selectedIndex = this.findNextSelectableIndex(-1, 1);
|
|
324
|
-
this.render(false);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// --- Implementation: Checkbox Prompt ---
|
|
329
|
-
class CheckboxPrompt extends Prompt {
|
|
330
|
-
constructor(options) {
|
|
331
|
-
super(options);
|
|
332
|
-
this.selectedIndex = 0;
|
|
333
|
-
this.errorMsg = '';
|
|
334
|
-
this.checkedState = options.choices.map(c => !!c.selected);
|
|
335
|
-
}
|
|
336
|
-
render(firstRender) {
|
|
337
|
-
// Ensure cursor is HIDDEN for menus
|
|
338
|
-
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
339
|
-
if (!firstRender) {
|
|
340
|
-
const extraLines = this.errorMsg ? 1 : 0;
|
|
341
|
-
this.print(`\x1b[${this.options.choices.length + 1 + extraLines}A`);
|
|
342
|
-
}
|
|
343
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
344
|
-
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
345
|
-
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`);
|
|
346
|
-
this.options.choices.forEach((choice, index) => {
|
|
347
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
348
|
-
const cursor = index === this.selectedIndex ? `${MepCLI.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
|
|
349
|
-
const isChecked = this.checkedState[index];
|
|
350
|
-
const checkbox = isChecked
|
|
351
|
-
? `${MepCLI.theme.success}◉${ansi_1.ANSI.RESET}`
|
|
352
|
-
: `${MepCLI.theme.muted}◯${ansi_1.ANSI.RESET}`;
|
|
353
|
-
const title = index === this.selectedIndex
|
|
354
|
-
? `${MepCLI.theme.main}${choice.title}${ansi_1.ANSI.RESET}`
|
|
355
|
-
: choice.title;
|
|
356
|
-
this.print(`${cursor} ${checkbox} ${title}\n`);
|
|
357
|
-
});
|
|
358
|
-
if (this.errorMsg) {
|
|
359
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
360
|
-
}
|
|
361
|
-
else if (!firstRender) {
|
|
362
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}`);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
handleInput(char) {
|
|
366
|
-
if (char === '\r' || char === '\n') {
|
|
367
|
-
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
368
|
-
const { min = 0, max } = this.options;
|
|
369
|
-
if (selectedCount < min) {
|
|
370
|
-
this.errorMsg = `You must select at least ${min} options.`;
|
|
371
|
-
this.render(false);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (max && selectedCount > max) {
|
|
375
|
-
this.errorMsg = `You can only select up to ${max} options.`;
|
|
376
|
-
this.render(false);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
this.cleanup();
|
|
380
|
-
this.print(ansi_1.ANSI.SHOW_CURSOR + '\n');
|
|
381
|
-
const results = this.options.choices
|
|
382
|
-
.filter((_, i) => this.checkedState[i])
|
|
383
|
-
.map(c => c.value);
|
|
384
|
-
if (this._resolve)
|
|
385
|
-
this._resolve(results);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (char === ' ') {
|
|
389
|
-
const currentChecked = this.checkedState[this.selectedIndex];
|
|
390
|
-
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
391
|
-
const { max } = this.options;
|
|
392
|
-
if (!currentChecked && max && selectedCount >= max) {
|
|
393
|
-
this.errorMsg = `Max ${max} selections allowed.`;
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
this.checkedState[this.selectedIndex] = !currentChecked;
|
|
397
|
-
this.errorMsg = '';
|
|
398
|
-
}
|
|
399
|
-
this.render(false);
|
|
400
|
-
}
|
|
401
|
-
if (char === '\u001b[A') { // Up
|
|
402
|
-
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.choices.length - 1;
|
|
403
|
-
this.errorMsg = '';
|
|
404
|
-
this.render(false);
|
|
405
|
-
}
|
|
406
|
-
if (char === '\u001b[B') { // Down
|
|
407
|
-
this.selectedIndex = this.selectedIndex < this.options.choices.length - 1 ? this.selectedIndex + 1 : 0;
|
|
408
|
-
this.errorMsg = '';
|
|
409
|
-
this.render(false);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// --- Implementation: Confirm Prompt ---
|
|
414
|
-
class ConfirmPrompt extends Prompt {
|
|
415
|
-
constructor(options) {
|
|
416
|
-
super(options);
|
|
417
|
-
this.value = options.initial ?? true;
|
|
418
|
-
}
|
|
419
|
-
render(firstRender) {
|
|
420
|
-
// Hide cursor for confirm, user just hits Y/N or Enter
|
|
421
|
-
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
422
|
-
if (!firstRender) {
|
|
423
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
424
|
-
}
|
|
425
|
-
const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
|
|
426
|
-
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} `);
|
|
427
|
-
const text = this.value ? 'Yes' : 'No';
|
|
428
|
-
this.print(`${MepCLI.theme.main}${text}${ansi_1.ANSI.RESET}\x1b[${text.length}D`);
|
|
429
|
-
}
|
|
430
|
-
handleInput(char) {
|
|
431
|
-
const c = char.toLowerCase();
|
|
432
|
-
if (c === '\r' || c === '\n') {
|
|
433
|
-
this.submit(this.value);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
if (c === 'y') {
|
|
437
|
-
this.value = true;
|
|
438
|
-
this.render(false);
|
|
439
|
-
}
|
|
440
|
-
if (c === 'n') {
|
|
441
|
-
this.value = false;
|
|
442
|
-
this.render(false);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
// --- Implementation: Toggle Prompt ---
|
|
447
|
-
class TogglePrompt extends Prompt {
|
|
448
|
-
constructor(options) {
|
|
449
|
-
super(options);
|
|
450
|
-
this.value = options.initial ?? false;
|
|
451
|
-
}
|
|
452
|
-
render(firstRender) {
|
|
453
|
-
this.print(ansi_1.ANSI.HIDE_CURSOR);
|
|
454
|
-
if (!firstRender) {
|
|
455
|
-
this.print(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
|
|
456
|
-
}
|
|
457
|
-
const activeText = this.options.activeText || 'ON';
|
|
458
|
-
const inactiveText = this.options.inactiveText || 'OFF';
|
|
459
|
-
let toggleDisplay = '';
|
|
460
|
-
if (this.value) {
|
|
461
|
-
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}`;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
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}`;
|
|
465
|
-
}
|
|
466
|
-
this.print(`${MepCLI.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${toggleDisplay}`);
|
|
467
|
-
this.print(`\x1b[${toggleDisplay.length}D`); // Move back is not really needed as we hide cursor, but kept for consistency
|
|
468
|
-
}
|
|
469
|
-
handleInput(char) {
|
|
470
|
-
if (char === '\r' || char === '\n') {
|
|
471
|
-
this.submit(this.value);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
if (char === '\u001b[D' || char === '\u001b[C' || char === 'h' || char === 'l') { // Left/Right
|
|
475
|
-
this.value = !this.value;
|
|
476
|
-
this.render(false);
|
|
477
|
-
}
|
|
478
|
-
if (char === ' ') {
|
|
479
|
-
this.value = !this.value;
|
|
480
|
-
this.render(false);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
// --- Implementation: Number Prompt ---
|
|
485
|
-
class NumberPrompt extends Prompt {
|
|
486
|
-
constructor(options) {
|
|
487
|
-
super(options);
|
|
488
|
-
this.cursor = 0;
|
|
489
|
-
this.errorMsg = '';
|
|
490
|
-
this.value = options.initial ?? 0;
|
|
491
|
-
this.stringValue = this.value.toString();
|
|
492
|
-
this.cursor = this.stringValue.length;
|
|
493
|
-
}
|
|
494
|
-
render(firstRender) {
|
|
495
|
-
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
496
|
-
if (!firstRender) {
|
|
497
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
498
|
-
if (this.errorMsg) {
|
|
499
|
-
this.print(ansi_1.ANSI.UP + ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
// 1. Render the Prompt Message
|
|
503
|
-
this.print(ansi_1.ANSI.ERASE_LINE + ansi_1.ANSI.CURSOR_LEFT);
|
|
504
|
-
const icon = this.errorMsg ? `${MepCLI.theme.error}✖` : `${MepCLI.theme.success}?`;
|
|
505
|
-
this.print(`${icon} ${ansi_1.ANSI.BOLD}${MepCLI.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `);
|
|
506
|
-
// 2. Render the Value
|
|
507
|
-
this.print(`${MepCLI.theme.main}${this.stringValue}${ansi_1.ANSI.RESET}`);
|
|
508
|
-
// 3. Handle Error Message
|
|
509
|
-
if (this.errorMsg) {
|
|
510
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${MepCLI.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
511
|
-
this.print(ansi_1.ANSI.UP);
|
|
512
|
-
const promptLen = this.options.message.length + 3;
|
|
513
|
-
const valLen = this.stringValue.length;
|
|
514
|
-
this.print(`\x1b[1000D\x1b[${promptLen + valLen}C`);
|
|
515
|
-
}
|
|
516
|
-
// 4. Position Cursor
|
|
517
|
-
const diff = this.stringValue.length - this.cursor;
|
|
518
|
-
if (diff > 0) {
|
|
519
|
-
this.print(`\x1b[${diff}D`);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
handleInput(char) {
|
|
523
|
-
// Enter
|
|
524
|
-
if (char === '\r' || char === '\n') {
|
|
525
|
-
const num = parseFloat(this.stringValue);
|
|
526
|
-
if (isNaN(num)) {
|
|
527
|
-
this.errorMsg = 'Please enter a valid number.';
|
|
528
|
-
this.render(false);
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
if (this.options.min !== undefined && num < this.options.min) {
|
|
532
|
-
this.errorMsg = `Minimum value is ${this.options.min}`;
|
|
533
|
-
this.render(false);
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
if (this.options.max !== undefined && num > this.options.max) {
|
|
537
|
-
this.errorMsg = `Maximum value is ${this.options.max}`;
|
|
538
|
-
this.render(false);
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
if (this.errorMsg) {
|
|
542
|
-
this.print(`\n${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.UP}`);
|
|
543
|
-
}
|
|
544
|
-
this.submit(num);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
// Up Arrow (Increment)
|
|
548
|
-
if (char === '\u001b[A') {
|
|
549
|
-
let num = parseFloat(this.stringValue) || 0;
|
|
550
|
-
num += (this.options.step ?? 1);
|
|
551
|
-
if (this.options.max !== undefined && num > this.options.max)
|
|
552
|
-
num = this.options.max;
|
|
553
|
-
this.stringValue = num.toString();
|
|
554
|
-
this.cursor = this.stringValue.length;
|
|
555
|
-
this.errorMsg = '';
|
|
556
|
-
this.render(false);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
// Down Arrow (Decrement)
|
|
560
|
-
if (char === '\u001b[B') {
|
|
561
|
-
let num = parseFloat(this.stringValue) || 0;
|
|
562
|
-
num -= (this.options.step ?? 1);
|
|
563
|
-
if (this.options.min !== undefined && num < this.options.min)
|
|
564
|
-
num = this.options.min;
|
|
565
|
-
this.stringValue = num.toString();
|
|
566
|
-
this.cursor = this.stringValue.length;
|
|
567
|
-
this.errorMsg = '';
|
|
568
|
-
this.render(false);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
// Backspace
|
|
572
|
-
if (char === '\u0008' || char === '\x7f') {
|
|
573
|
-
if (this.cursor > 0) {
|
|
574
|
-
this.stringValue = this.stringValue.slice(0, this.cursor - 1) + this.stringValue.slice(this.cursor);
|
|
575
|
-
this.cursor--;
|
|
576
|
-
this.errorMsg = '';
|
|
577
|
-
this.render(false);
|
|
578
|
-
}
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
// Arrow Left
|
|
582
|
-
if (char === '\u001b[D') {
|
|
583
|
-
if (this.cursor > 0) {
|
|
584
|
-
this.cursor--;
|
|
585
|
-
this.render(false);
|
|
586
|
-
}
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
// Arrow Right
|
|
590
|
-
if (char === '\u001b[C') {
|
|
591
|
-
if (this.cursor < this.stringValue.length) {
|
|
592
|
-
this.cursor++;
|
|
593
|
-
this.render(false);
|
|
594
|
-
}
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
// Numeric Input (and . and -)
|
|
598
|
-
if (/^[0-9.\-]$/.test(char)) {
|
|
599
|
-
if (char === '-' && (this.cursor !== 0 || this.stringValue.includes('-')))
|
|
600
|
-
return;
|
|
601
|
-
if (char === '.' && this.stringValue.includes('.'))
|
|
602
|
-
return;
|
|
603
|
-
this.stringValue = this.stringValue.slice(0, this.cursor) + char + this.stringValue.slice(this.cursor);
|
|
604
|
-
this.cursor++;
|
|
605
|
-
this.errorMsg = '';
|
|
606
|
-
this.render(false);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
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");
|
|
610
17
|
/**
|
|
611
18
|
* Public Facade for MepCLI
|
|
612
19
|
*/
|
|
@@ -637,32 +44,41 @@ class MepCLI {
|
|
|
637
44
|
}
|
|
638
45
|
}
|
|
639
46
|
static text(options) {
|
|
640
|
-
return new TextPrompt(options).run();
|
|
47
|
+
return new text_1.TextPrompt(options).run();
|
|
641
48
|
}
|
|
642
49
|
static select(options) {
|
|
643
|
-
return new SelectPrompt(options).run();
|
|
50
|
+
return new select_1.SelectPrompt(options).run();
|
|
644
51
|
}
|
|
645
52
|
static checkbox(options) {
|
|
646
|
-
return new CheckboxPrompt(options).run();
|
|
53
|
+
return new checkbox_1.CheckboxPrompt(options).run();
|
|
647
54
|
}
|
|
648
55
|
static confirm(options) {
|
|
649
|
-
return new ConfirmPrompt(options).run();
|
|
56
|
+
return new confirm_1.ConfirmPrompt(options).run();
|
|
650
57
|
}
|
|
651
58
|
static password(options) {
|
|
652
|
-
return new TextPrompt({ ...options, isPassword: true }).run();
|
|
59
|
+
return new text_1.TextPrompt({ ...options, isPassword: true }).run();
|
|
653
60
|
}
|
|
654
61
|
static number(options) {
|
|
655
|
-
return new NumberPrompt(options).run();
|
|
62
|
+
return new number_1.NumberPrompt(options).run();
|
|
656
63
|
}
|
|
657
64
|
static toggle(options) {
|
|
658
|
-
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();
|
|
659
81
|
}
|
|
660
82
|
}
|
|
661
83
|
exports.MepCLI = MepCLI;
|
|
662
|
-
MepCLI.theme =
|
|
663
|
-
main: ansi_1.ANSI.FG_CYAN,
|
|
664
|
-
success: ansi_1.ANSI.FG_GREEN,
|
|
665
|
-
error: ansi_1.ANSI.FG_RED,
|
|
666
|
-
muted: ansi_1.ANSI.FG_GRAY,
|
|
667
|
-
title: ansi_1.ANSI.RESET
|
|
668
|
-
};
|
|
84
|
+
MepCLI.theme = theme_1.theme;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { CheckboxOptions } from '../types';
|
|
3
|
+
export declare class CheckboxPrompt extends Prompt<any[], CheckboxOptions> {
|
|
4
|
+
private selectedIndex;
|
|
5
|
+
private checkedState;
|
|
6
|
+
private errorMsg;
|
|
7
|
+
constructor(options: CheckboxOptions);
|
|
8
|
+
protected render(firstRender: boolean): void;
|
|
9
|
+
protected handleInput(char: string): void;
|
|
10
|
+
}
|