mepcli 0.5.5 → 0.6.0

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 (75) hide show
  1. package/README.md +173 -6
  2. package/dist/ansi.d.ts +1 -0
  3. package/dist/ansi.js +1 -0
  4. package/dist/base.d.ts +1 -1
  5. package/dist/base.js +1 -10
  6. package/dist/core.d.ts +23 -1
  7. package/dist/core.js +60 -0
  8. package/dist/highlight.d.ts +1 -0
  9. package/dist/highlight.js +40 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/input.js +26 -14
  13. package/dist/prompts/autocomplete.d.ts +1 -1
  14. package/dist/prompts/autocomplete.js +2 -7
  15. package/dist/prompts/calendar.d.ts +20 -0
  16. package/dist/prompts/calendar.js +329 -0
  17. package/dist/prompts/checkbox.d.ts +1 -1
  18. package/dist/prompts/checkbox.js +38 -8
  19. package/dist/prompts/code.d.ts +17 -0
  20. package/dist/prompts/code.js +210 -0
  21. package/dist/prompts/color.d.ts +14 -0
  22. package/dist/prompts/color.js +147 -0
  23. package/dist/prompts/confirm.d.ts +1 -1
  24. package/dist/prompts/confirm.js +1 -1
  25. package/dist/prompts/cron.d.ts +13 -0
  26. package/dist/prompts/cron.js +176 -0
  27. package/dist/prompts/date.d.ts +1 -1
  28. package/dist/prompts/date.js +15 -5
  29. package/dist/prompts/editor.js +2 -2
  30. package/dist/prompts/file.d.ts +7 -0
  31. package/dist/prompts/file.js +56 -60
  32. package/dist/prompts/form.d.ts +17 -0
  33. package/dist/prompts/form.js +225 -0
  34. package/dist/prompts/grid.d.ts +14 -0
  35. package/dist/prompts/grid.js +178 -0
  36. package/dist/prompts/keypress.d.ts +2 -2
  37. package/dist/prompts/keypress.js +2 -2
  38. package/dist/prompts/list.d.ts +1 -1
  39. package/dist/prompts/list.js +42 -22
  40. package/dist/prompts/multi-select.d.ts +1 -1
  41. package/dist/prompts/multi-select.js +39 -4
  42. package/dist/prompts/number.d.ts +1 -1
  43. package/dist/prompts/number.js +2 -2
  44. package/dist/prompts/range.d.ts +9 -0
  45. package/dist/prompts/range.js +140 -0
  46. package/dist/prompts/rating.d.ts +1 -1
  47. package/dist/prompts/rating.js +1 -1
  48. package/dist/prompts/select.d.ts +1 -1
  49. package/dist/prompts/select.js +1 -1
  50. package/dist/prompts/slider.d.ts +1 -1
  51. package/dist/prompts/slider.js +1 -1
  52. package/dist/prompts/snippet.d.ts +18 -0
  53. package/dist/prompts/snippet.js +203 -0
  54. package/dist/prompts/sort.d.ts +1 -1
  55. package/dist/prompts/sort.js +1 -4
  56. package/dist/prompts/spam.d.ts +17 -0
  57. package/dist/prompts/spam.js +62 -0
  58. package/dist/prompts/table.d.ts +1 -1
  59. package/dist/prompts/table.js +1 -1
  60. package/dist/prompts/text.d.ts +1 -0
  61. package/dist/prompts/text.js +13 -31
  62. package/dist/prompts/toggle.d.ts +1 -1
  63. package/dist/prompts/toggle.js +1 -1
  64. package/dist/prompts/transfer.d.ts +18 -0
  65. package/dist/prompts/transfer.js +203 -0
  66. package/dist/prompts/tree-select.d.ts +32 -0
  67. package/dist/prompts/tree-select.js +277 -0
  68. package/dist/prompts/tree.d.ts +3 -3
  69. package/dist/prompts/tree.js +27 -19
  70. package/dist/prompts/wait.d.ts +18 -0
  71. package/dist/prompts/wait.js +62 -0
  72. package/dist/types.d.ts +84 -0
  73. package/dist/utils.js +1 -1
  74. package/example.ts +150 -15
  75. package/package.json +2 -2
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CalendarPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ class CalendarPrompt extends base_1.Prompt {
8
+ constructor(options) {
9
+ super(options);
10
+ this.selection = null;
11
+ this.selectingRange = false; // Internal state for range selection
12
+ // Normalize initial value
13
+ if (Array.isArray(options.initial)) {
14
+ this.selection = [new Date(options.initial[0]), new Date(options.initial[1])];
15
+ this.cursor = new Date(options.initial[1]); // Cursor at end of range
16
+ }
17
+ else if (options.initial) {
18
+ this.selection = new Date(options.initial);
19
+ this.cursor = new Date(options.initial);
20
+ }
21
+ else {
22
+ this.cursor = new Date();
23
+ // If range mode but no initial, selection remains null until user picks
24
+ if (this.options.mode !== 'range') {
25
+ this.selection = new Date();
26
+ }
27
+ }
28
+ // Clone cursor to viewDate to track month view independently
29
+ this.viewDate = new Date(this.cursor);
30
+ this.viewDate.setDate(1);
31
+ }
32
+ getDaysInMonth(year, month) {
33
+ return new Date(year, month + 1, 0).getDate();
34
+ }
35
+ getDayOfWeek(year, month, day) {
36
+ return new Date(year, month, day).getDay();
37
+ }
38
+ generateMonthGrid(year, month) {
39
+ const days = [];
40
+ const firstDayOfMonth = new Date(year, month, 1);
41
+ const startDayOfWeek = firstDayOfMonth.getDay(); // 0 (Sun) to 6 (Sat)
42
+ const weekStart = this.options.weekStart || 0;
43
+ // Calculate days from previous month to fill the first row
44
+ // If startDayOfWeek is 2 (Tue) and weekStart is 0 (Sun), we need 2 days from prev month.
45
+ // If startDayOfWeek is 0 (Sun) and weekStart is 1 (Mon), we need 6 days from prev month.
46
+ const daysFromPrevMonth = (startDayOfWeek - weekStart + 7) % 7;
47
+ if (daysFromPrevMonth === 0 && startDayOfWeek !== weekStart) {
48
+ // Logic check: if starts on same day, 0.
49
+ }
50
+ const prevMonthDate = new Date(year, month, 0); // Last day of prev month
51
+ const prevMonthDaysCount = prevMonthDate.getDate();
52
+ // Add previous month days
53
+ for (let i = daysFromPrevMonth - 1; i >= 0; i--) {
54
+ days.push({
55
+ date: new Date(year, month - 1, prevMonthDaysCount - i),
56
+ inMonth: false
57
+ });
58
+ }
59
+ // Add current month days
60
+ const daysInMonth = this.getDaysInMonth(year, month);
61
+ for (let i = 1; i <= daysInMonth; i++) {
62
+ days.push({
63
+ date: new Date(year, month, i),
64
+ inMonth: true
65
+ });
66
+ }
67
+ // Add next month days to fill 42 cells (6 rows * 7 cols)
68
+ let nextMonthDay = 1;
69
+ while (days.length < 42) {
70
+ days.push({
71
+ date: new Date(year, month + 1, nextMonthDay++),
72
+ inMonth: false
73
+ });
74
+ }
75
+ return days;
76
+ }
77
+ isSameDay(d1, d2) {
78
+ return d1.getFullYear() === d2.getFullYear() &&
79
+ d1.getMonth() === d2.getMonth() &&
80
+ d1.getDate() === d2.getDate();
81
+ }
82
+ isSelected(d) {
83
+ if (!this.selection)
84
+ return false;
85
+ if (this.options.mode === 'range') {
86
+ if (Array.isArray(this.selection)) {
87
+ const [start, end] = this.selection;
88
+ // If the range is complete, highlight everything in between
89
+ // Sort to ensure valid range check even if start > end (though we should normalize)
90
+ const s = start < end ? start : end;
91
+ const e = start < end ? end : start;
92
+ // Set times to midnight for comparison
93
+ const dTime = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
94
+ const sTime = new Date(s.getFullYear(), s.getMonth(), s.getDate()).getTime();
95
+ const eTime = new Date(e.getFullYear(), e.getMonth(), e.getDate()).getTime();
96
+ return dTime >= sTime && dTime <= eTime;
97
+ }
98
+ else {
99
+ // Selecting range, but only first point picked?
100
+ // Usually when picking range, first click sets start, moving cursor highlights?
101
+ // For now, if selection is single date in range mode, just highlight it
102
+ return this.isSameDay(d, this.selection);
103
+ }
104
+ }
105
+ else {
106
+ return this.isSameDay(d, this.selection);
107
+ }
108
+ }
109
+ // Helper to check if selection is 'in progress' (only one end picked) for visual feedback
110
+ isRangeStart(d) {
111
+ if (this.options.mode !== 'range' || !this.selection)
112
+ return false;
113
+ if (Array.isArray(this.selection)) {
114
+ return this.isSameDay(d, this.selection[0]) || this.isSameDay(d, this.selection[1]);
115
+ }
116
+ return this.isSameDay(d, this.selection);
117
+ }
118
+ render(_firstRender) {
119
+ const monthNames = ["January", "February", "March", "April", "May", "June",
120
+ "July", "August", "September", "October", "November", "December"
121
+ ];
122
+ const year = this.viewDate.getFullYear();
123
+ const month = this.viewDate.getMonth();
124
+ const header = `${ansi_1.ANSI.BOLD}${monthNames[month]} ${year}${ansi_1.ANSI.RESET}`;
125
+ // Centered header roughly
126
+ // 20 is approx width of calendar (3 chars * 7 cols - 1 space = 20)
127
+ // Actually grid width: 7 columns. Each cell is usually "DD ". Last col "DD".
128
+ // Let's say cell width is 3 chars (2 digits + 1 space). Total 21 chars.
129
+ const weekDays = this.options.weekStart === 1
130
+ ? ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
131
+ : ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
132
+ const grid = this.generateMonthGrid(year, month);
133
+ let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
134
+ // Controls hint
135
+ output += `${theme_1.theme.muted}< ${header} >${ansi_1.ANSI.RESET}\n`;
136
+ // Weekday Header
137
+ output += weekDays.map(d => `${theme_1.theme.muted}${d}${ansi_1.ANSI.RESET}`).join(' ') + '\n';
138
+ // Grid
139
+ let rowLine = '';
140
+ for (let i = 0; i < grid.length; i++) {
141
+ const cell = grid[i];
142
+ const dateStr = cell.date.getDate().toString().padStart(2, ' ');
143
+ let style = '';
144
+ const isCursor = this.isSameDay(cell.date, this.cursor);
145
+ const isSel = this.isSelected(cell.date);
146
+ const isToday = this.isSameDay(cell.date, new Date());
147
+ // Base style
148
+ if (!cell.inMonth) {
149
+ style = theme_1.theme.muted;
150
+ }
151
+ if (isSel) {
152
+ // If it is selected, use main color
153
+ style = theme_1.theme.main;
154
+ }
155
+ if (isToday && !isSel) {
156
+ style += ansi_1.ANSI.UNDERLINE;
157
+ }
158
+ if (isCursor) {
159
+ style += ansi_1.ANSI.REVERSE; // Invert colors for cursor
160
+ }
161
+ // Apply
162
+ // Reset must be applied after each cell to prevent bleeding
163
+ rowLine += `${style}${dateStr}${ansi_1.ANSI.RESET}`;
164
+ if ((i + 1) % 7 === 0) {
165
+ output += rowLine + '\n';
166
+ rowLine = '';
167
+ }
168
+ else {
169
+ rowLine += ' ';
170
+ }
171
+ }
172
+ // Helper text
173
+ const help = this.options.mode === 'range'
174
+ ? 'Enter: Select start/end'
175
+ : 'Enter: Select';
176
+ output += `${theme_1.theme.muted}${help}${ansi_1.ANSI.RESET}`;
177
+ this.renderFrame(output);
178
+ }
179
+ syncViewDate() {
180
+ if (this.cursor.getMonth() !== this.viewDate.getMonth() || this.cursor.getFullYear() !== this.viewDate.getFullYear()) {
181
+ this.viewDate = new Date(this.cursor);
182
+ this.viewDate.setDate(1);
183
+ }
184
+ }
185
+ // When the user navigates the visible month independently (mouse scroll or
186
+ // '<'/'>'), keep the cursor in the newly viewed month so subsequent
187
+ // cursor movements don't snap the view back to the old month.
188
+ alignCursorToViewDate() {
189
+ const day = this.cursor.getDate();
190
+ const year = this.viewDate.getFullYear();
191
+ const month = this.viewDate.getMonth();
192
+ const daysInTarget = this.getDaysInMonth(year, month);
193
+ const newDay = Math.min(day, daysInTarget);
194
+ this.cursor = new Date(year, month, newDay);
195
+ }
196
+ handleInput(char, _key) {
197
+ const isUp = this.isUp(char);
198
+ const isDown = this.isDown(char);
199
+ const isLeft = this.isLeft(char);
200
+ const isRight = this.isRight(char);
201
+ // Navigation
202
+ if (isUp || isDown || isLeft || isRight) {
203
+ const d = new Date(this.cursor);
204
+ if (isUp)
205
+ d.setDate(d.getDate() - 7);
206
+ if (isDown)
207
+ d.setDate(d.getDate() + 7);
208
+ if (isLeft)
209
+ d.setDate(d.getDate() - 1);
210
+ if (isRight)
211
+ d.setDate(d.getDate() + 1);
212
+ this.cursor = d;
213
+ this.syncViewDate();
214
+ this.render(false);
215
+ return;
216
+ }
217
+ // PageUp (Month - 1)
218
+ if (char === '\x1b[5~') {
219
+ this.cursor.setMonth(this.cursor.getMonth() - 1);
220
+ this.syncViewDate();
221
+ this.render(false);
222
+ return;
223
+ }
224
+ // PageDown (Month + 1)
225
+ if (char === '\x1b[6~') {
226
+ this.cursor.setMonth(this.cursor.getMonth() + 1);
227
+ this.syncViewDate();
228
+ this.render(false);
229
+ return;
230
+ }
231
+ // Ctrl+Up (Year - 1)
232
+ if (char === '\x1b[1;5A') {
233
+ this.cursor.setFullYear(this.cursor.getFullYear() - 1);
234
+ this.syncViewDate();
235
+ this.render(false);
236
+ return;
237
+ }
238
+ // Ctrl+Down (Year + 1)
239
+ if (char === '\x1b[1;5B') {
240
+ this.cursor.setFullYear(this.cursor.getFullYear() + 1);
241
+ this.syncViewDate();
242
+ this.render(false);
243
+ return;
244
+ }
245
+ // Home (First Day of Month)
246
+ if (char === '\x1b[H' || char === '\x1b[1~') {
247
+ this.cursor.setDate(1);
248
+ this.render(false);
249
+ return;
250
+ }
251
+ // End (Last Day of Month)
252
+ if (char === '\x1b[F' || char === '\x1b[4~') {
253
+ this.cursor.setMonth(this.cursor.getMonth() + 1);
254
+ this.cursor.setDate(0);
255
+ this.render(false);
256
+ return;
257
+ }
258
+ // t (Today)
259
+ if (char === 't') {
260
+ this.cursor = new Date();
261
+ this.syncViewDate();
262
+ this.render(false);
263
+ return;
264
+ }
265
+ // Month Navigation with < and > (shift+, shift+.)
266
+ if (char === '<' || char === ',') {
267
+ this.viewDate.setMonth(this.viewDate.getMonth() - 1);
268
+ this.alignCursorToViewDate();
269
+ this.render(false);
270
+ return;
271
+ }
272
+ if (char === '>' || char === '.') {
273
+ this.viewDate.setMonth(this.viewDate.getMonth() + 1);
274
+ this.alignCursorToViewDate();
275
+ this.render(false);
276
+ return;
277
+ }
278
+ // Selection
279
+ if (char === '\r' || char === '\n' || char === ' ') {
280
+ if (this.options.mode === 'range') {
281
+ if (!this.selectingRange) {
282
+ // Start new range selection
283
+ this.selection = this.cursor; // First point (single date temporary)
284
+ this.selectingRange = true;
285
+ }
286
+ else {
287
+ // Finish range selection
288
+ const start = this.selection;
289
+ const end = this.cursor;
290
+ // Order them
291
+ if (start > end) {
292
+ this.selection = [end, start];
293
+ }
294
+ else {
295
+ this.selection = [start, end];
296
+ }
297
+ this.selectingRange = false;
298
+ this.submit(this.selection);
299
+ return;
300
+ }
301
+ }
302
+ else {
303
+ // Single mode
304
+ this.selection = this.cursor;
305
+ this.submit(this.selection);
306
+ return;
307
+ }
308
+ this.render(false);
309
+ return;
310
+ }
311
+ }
312
+ handleMouse(event) {
313
+ if (event.action === 'scroll') {
314
+ const direction = event.scroll === 'up' ? -1 : 1;
315
+ if (event.ctrl) {
316
+ // Ctrl+Scroll: Day (Cursor)
317
+ this.cursor.setDate(this.cursor.getDate() + direction);
318
+ this.syncViewDate();
319
+ }
320
+ else {
321
+ // Normal Scroll: Month
322
+ this.viewDate.setMonth(this.viewDate.getMonth() + direction);
323
+ this.alignCursorToViewDate();
324
+ }
325
+ this.render(false);
326
+ }
327
+ }
328
+ }
329
+ exports.CalendarPrompt = CalendarPrompt;
@@ -7,7 +7,7 @@ export declare class CheckboxPrompt<V> extends Prompt<any[], CheckboxOptions<V>>
7
7
  private scrollTop;
8
8
  private readonly pageSize;
9
9
  constructor(options: CheckboxOptions<V>);
10
- protected render(firstRender: boolean): void;
10
+ protected render(_firstRender: boolean): void;
11
11
  protected handleInput(char: string): void;
12
12
  protected handleMouse(event: MouseEvent): void;
13
13
  }
@@ -11,12 +11,12 @@ class CheckboxPrompt extends base_1.Prompt {
11
11
  super(options);
12
12
  this.selectedIndex = 0;
13
13
  this.errorMsg = '';
14
- // Pagination state (added for consistency and performance)
14
+ // Pagination state
15
15
  this.scrollTop = 0;
16
16
  this.pageSize = 10;
17
17
  this.checkedState = options.choices.map(c => !!c.selected);
18
18
  }
19
- render(firstRender) {
19
+ render(_firstRender) {
20
20
  // Adjust Scroll Top
21
21
  if (this.selectedIndex < this.scrollTop) {
22
22
  this.scrollTop = this.selectedIndex;
@@ -31,7 +31,7 @@ class CheckboxPrompt extends base_1.Prompt {
31
31
  let output = '';
32
32
  // Header
33
33
  const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
34
- output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Press <space> to select, <enter> to confirm)${ansi_1.ANSI.RESET}`;
34
+ output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Space: toggle, a: all, x: none, Enter: submit)${ansi_1.ANSI.RESET}`;
35
35
  // List
36
36
  const choices = this.options.choices;
37
37
  const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
@@ -50,7 +50,7 @@ class CheckboxPrompt extends base_1.Prompt {
50
50
  });
51
51
  // Indication of more items
52
52
  if (choices.length > this.pageSize) {
53
- const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
53
+ // const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
54
54
  }
55
55
  if (this.errorMsg) {
56
56
  output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
@@ -72,10 +72,6 @@ class CheckboxPrompt extends base_1.Prompt {
72
72
  return;
73
73
  }
74
74
  this.cleanup();
75
- // renderFrame cleans up lines, but doesn't print the final state "persisted" if we want to show the result?
76
- // Usually we clear the prompt or show a summary.
77
- // MepCLI seems to submit and let the caller decide or just print newline.
78
- // Base `submit` prints newline.
79
75
  const results = this.options.choices
80
76
  .filter((_, i) => this.checkedState[i])
81
77
  .map(c => c.value);
@@ -83,6 +79,40 @@ class CheckboxPrompt extends base_1.Prompt {
83
79
  this._resolve(results);
84
80
  return;
85
81
  }
82
+ // --- Batch Shortcuts ---
83
+ // 'a': Select All
84
+ if (char === 'a') {
85
+ if (this.options.max && this.options.choices.length > this.options.max) {
86
+ this.errorMsg = `Cannot select all: Max limit is ${this.options.max}`;
87
+ }
88
+ else {
89
+ this.checkedState.fill(true);
90
+ this.errorMsg = '';
91
+ }
92
+ this.render(false);
93
+ return;
94
+ }
95
+ // 'x' or 'n': Select None
96
+ if (char === 'x' || char === 'n') {
97
+ this.checkedState.fill(false);
98
+ this.errorMsg = '';
99
+ this.render(false);
100
+ return;
101
+ }
102
+ // 'i': Invert Selection
103
+ if (char === 'i') {
104
+ const potentialCount = this.checkedState.filter(s => !s).length;
105
+ if (this.options.max && potentialCount > this.options.max) {
106
+ this.errorMsg = `Cannot invert: Result exceeds max limit ${this.options.max}`;
107
+ }
108
+ else {
109
+ this.checkedState = this.checkedState.map(s => !s);
110
+ this.errorMsg = '';
111
+ }
112
+ this.render(false);
113
+ return;
114
+ }
115
+ // Space Toggle
86
116
  if (char === ' ') {
87
117
  const currentChecked = this.checkedState[this.selectedIndex];
88
118
  const selectedCount = this.checkedState.filter(Boolean).length;
@@ -0,0 +1,17 @@
1
+ import { Prompt } from '../base';
2
+ import { CodeOptions, MouseEvent } from '../types';
3
+ export declare class CodePrompt extends Prompt<string, CodeOptions> {
4
+ private tokens;
5
+ private variableTokens;
6
+ private values;
7
+ private activeVarIndex;
8
+ private cursor;
9
+ private lastLinesUp;
10
+ constructor(options: CodeOptions);
11
+ private parseTemplate;
12
+ protected render(firstRender: boolean): void;
13
+ protected handleInput(char: string, _key: Buffer): void;
14
+ protected handleMouse(event: MouseEvent): void;
15
+ private moveFocus;
16
+ private submitCode;
17
+ }
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodePrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ const highlight_1 = require("../highlight");
8
+ class CodePrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.tokens = [];
12
+ this.variableTokens = [];
13
+ this.values = {};
14
+ this.activeVarIndex = 0;
15
+ this.cursor = 0;
16
+ this.lastLinesUp = 0;
17
+ this.parseTemplate();
18
+ // Init values
19
+ this.variableTokens.forEach(idx => {
20
+ const name = this.tokens[idx].value;
21
+ this.values[name] = '';
22
+ });
23
+ // Init cursor at end of first var
24
+ if (this.variableTokens.length > 0) {
25
+ this.cursor = 0; // Start empty
26
+ }
27
+ }
28
+ parseTemplate() {
29
+ const regex = /\$\{([a-zA-Z0-9_]+)\}/g;
30
+ let lastIndex = 0;
31
+ let match;
32
+ while ((match = regex.exec(this.options.template)) !== null) {
33
+ if (match.index > lastIndex) {
34
+ this.tokens.push({ type: 'static', value: this.options.template.substring(lastIndex, match.index) });
35
+ }
36
+ this.tokens.push({ type: 'variable', value: match[1] });
37
+ this.variableTokens.push(this.tokens.length - 1);
38
+ lastIndex = regex.lastIndex;
39
+ }
40
+ if (lastIndex < this.options.template.length) {
41
+ this.tokens.push({ type: 'static', value: this.options.template.substring(lastIndex) });
42
+ }
43
+ }
44
+ render(firstRender) {
45
+ // Reset cursor from previous render relative position
46
+ if (!firstRender && this.lastLinesUp > 0) {
47
+ this.print(`\x1b[${this.lastLinesUp}B`);
48
+ }
49
+ this.lastLinesUp = 0;
50
+ // 1. Construct Raw String for Highlighting
51
+ const ACTIVE_PLACEHOLDER = '___ACTIVE___';
52
+ let rawWithPlaceholder = '';
53
+ this.tokens.forEach((token, idx) => {
54
+ if (token.type === 'static') {
55
+ rawWithPlaceholder += token.value;
56
+ }
57
+ else {
58
+ if (this.variableTokens[this.activeVarIndex] === idx) {
59
+ rawWithPlaceholder += ACTIVE_PLACEHOLDER;
60
+ }
61
+ else {
62
+ rawWithPlaceholder += this.values[token.value] || '';
63
+ }
64
+ }
65
+ });
66
+ // 2. Highlight
67
+ let highlighted = '';
68
+ const shouldHighlight = this.options.highlight !== false; // Default true
69
+ if (shouldHighlight) {
70
+ highlighted = (0, highlight_1.highlightJson)(rawWithPlaceholder);
71
+ }
72
+ else {
73
+ highlighted = rawWithPlaceholder;
74
+ }
75
+ // 3. Replace Placeholder with Styled Active Value
76
+ const activeVarName = this.tokens[this.variableTokens[this.activeVarIndex]].value;
77
+ const activeVal = this.values[activeVarName] || '';
78
+ // Use Main color + Underline. RESET restores default.
79
+ const styledActive = `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${activeVal}${ansi_1.ANSI.RESET}`;
80
+ highlighted = highlighted.replace(ACTIVE_PLACEHOLDER, styledActive);
81
+ // 4. Output
82
+ const prefix = `${theme_1.theme.success}? ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
83
+ const suffix = `\n${theme_1.theme.muted}(Tab to next, Enter to submit)${ansi_1.ANSI.RESET}`;
84
+ const fullOutput = prefix + highlighted + suffix;
85
+ this.renderFrame(fullOutput);
86
+ // 5. Cursor Calculation
87
+ // Calculate (row, col) relative to start of snippet
88
+ let textBeforeCursor = '';
89
+ for (let i = 0; i < this.tokens.length; i++) {
90
+ const token = this.tokens[i];
91
+ if (token.type === 'static') {
92
+ textBeforeCursor += token.value;
93
+ }
94
+ else {
95
+ if (this.variableTokens[this.activeVarIndex] === i) {
96
+ textBeforeCursor += activeVal.substring(0, this.cursor);
97
+ break;
98
+ }
99
+ else {
100
+ textBeforeCursor += this.values[token.value] || '';
101
+ }
102
+ }
103
+ }
104
+ const rowsBefore = textBeforeCursor.split('\n');
105
+ const cursorRow = rowsBefore.length - 1;
106
+ const cursorCol = rowsBefore[rowsBefore.length - 1].length;
107
+ // Calculate total lines in snippet
108
+ let fullRaw = '';
109
+ this.tokens.forEach(token => {
110
+ fullRaw += (token.type === 'static' ? token.value : (this.values[token.value] || ''));
111
+ });
112
+ const totalSnippetLines = fullRaw.split('\n').length;
113
+ // Calculate linesUp from the bottom of snippet
114
+ // Suffix is 1 line.
115
+ // CursorRow is 0-based index from top of snippet.
116
+ // If cursorRow is at bottom (totalSnippetLines-1), linesUp = 1 (Suffix).
117
+ // If cursorRow is at top (0), linesUp = 1 + (totalSnippetLines - 1).
118
+ const linesUp = 1 + (totalSnippetLines - 1 - cursorRow);
119
+ this.print(ansi_1.ANSI.SHOW_CURSOR);
120
+ if (linesUp > 0) {
121
+ this.print(`\x1b[${linesUp}A`);
122
+ this.lastLinesUp = linesUp;
123
+ }
124
+ this.print(ansi_1.ANSI.CURSOR_LEFT);
125
+ if (cursorCol > 0) {
126
+ this.print(`\x1b[${cursorCol}C`);
127
+ }
128
+ }
129
+ handleInput(char, _key) {
130
+ // Nav
131
+ if (char === '\u001b[Z') { // Shift Tab
132
+ this.moveFocus(-1);
133
+ return;
134
+ }
135
+ if (char === '\t') {
136
+ this.moveFocus(1);
137
+ return;
138
+ }
139
+ // Enter
140
+ if (char === '\r' || char === '\n') {
141
+ this.submitCode();
142
+ return;
143
+ }
144
+ const activeTokenIdx = this.variableTokens[this.activeVarIndex];
145
+ const varName = this.tokens[activeTokenIdx].value;
146
+ const val = this.values[varName] || '';
147
+ // Editing
148
+ if (char === '\u0008' || char === '\x7f') { // Backspace
149
+ if (this.cursor > 0) {
150
+ const pre = val.slice(0, this.cursor - 1);
151
+ const post = val.slice(this.cursor);
152
+ this.values[varName] = pre + post;
153
+ this.cursor--;
154
+ this.render(false);
155
+ }
156
+ return;
157
+ }
158
+ if (this.isLeft(char)) {
159
+ if (this.cursor > 0)
160
+ this.cursor--;
161
+ this.render(false);
162
+ return;
163
+ }
164
+ if (this.isRight(char)) {
165
+ if (this.cursor < val.length)
166
+ this.cursor++;
167
+ this.render(false);
168
+ return;
169
+ }
170
+ if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
171
+ const pre = val.slice(0, this.cursor);
172
+ const post = val.slice(this.cursor);
173
+ this.values[varName] = pre + char + post;
174
+ this.cursor += char.length;
175
+ this.render(false);
176
+ }
177
+ }
178
+ handleMouse(event) {
179
+ if (event.action === 'scroll') {
180
+ if (event.scroll === 'up') {
181
+ this.moveFocus(-1);
182
+ }
183
+ else if (event.scroll === 'down') {
184
+ this.moveFocus(1);
185
+ }
186
+ }
187
+ }
188
+ moveFocus(direction) {
189
+ const nextIndex = this.activeVarIndex + direction;
190
+ if (nextIndex >= 0 && nextIndex < this.variableTokens.length) {
191
+ this.activeVarIndex = nextIndex;
192
+ const varName = this.tokens[this.variableTokens[this.activeVarIndex]].value;
193
+ this.cursor = (this.values[varName] || '').length; // Move cursor to end
194
+ this.render(false);
195
+ }
196
+ }
197
+ submitCode() {
198
+ let result = '';
199
+ this.tokens.forEach(token => {
200
+ if (token.type === 'static') {
201
+ result += token.value;
202
+ }
203
+ else {
204
+ result += this.values[token.value] || '';
205
+ }
206
+ });
207
+ this.submit(result);
208
+ }
209
+ }
210
+ exports.CodePrompt = CodePrompt;
@@ -0,0 +1,14 @@
1
+ import { Prompt } from '../base';
2
+ import { ColorOptions, MouseEvent } from '../types';
3
+ export declare class ColorPrompt extends Prompt<string, ColorOptions> {
4
+ private rgb;
5
+ private activeChannel;
6
+ private inputBuffer;
7
+ constructor(options: ColorOptions);
8
+ private parseHex;
9
+ private rgbToHex;
10
+ private getBgColorCode;
11
+ protected render(_firstRender: boolean): void;
12
+ protected handleInput(char: string, _key: Buffer): void;
13
+ protected handleMouse(event: MouseEvent): void;
14
+ }