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,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ColorPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ class ColorPrompt extends base_1.Prompt {
8
+ constructor(options) {
9
+ super(options);
10
+ this.activeChannel = 'r';
11
+ this.inputBuffer = '';
12
+ this.rgb = this.parseHex(options.initial || '#000000');
13
+ }
14
+ parseHex(hex) {
15
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
16
+ return result ? {
17
+ r: parseInt(result[1], 16),
18
+ g: parseInt(result[2], 16),
19
+ b: parseInt(result[3], 16)
20
+ } : { r: 0, g: 0, b: 0 };
21
+ }
22
+ rgbToHex(r, g, b) {
23
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
24
+ }
25
+ // Helper to format ANSI TrueColor background
26
+ getBgColorCode(r, g, b) {
27
+ return `\x1b[48;2;${r};${g};${b}m`;
28
+ }
29
+ render(_firstRender) {
30
+ const { r, g, b } = this.rgb;
31
+ const hex = this.rgbToHex(r, g, b);
32
+ let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
33
+ output += `${ansi_1.ANSI.BOLD}${hex}${ansi_1.ANSI.RESET}\n\n`;
34
+ // Preview Block
35
+ if (this.capabilities.hasTrueColor) {
36
+ const bg = this.getBgColorCode(r, g, b);
37
+ const block = `${bg} ${ansi_1.ANSI.RESET}`; // 8 spaces
38
+ output += ` ${block}\n`;
39
+ output += ` ${block}\n`;
40
+ output += ` ${block}\n\n`;
41
+ }
42
+ else {
43
+ // Fallback for no true color?
44
+ // Maybe just show text or use closest 256 color?
45
+ // For now, simpler fallback:
46
+ output += ` (Preview unavailable in this terminal)\n\n`;
47
+ }
48
+ // Sliders
49
+ const channels = ['r', 'g', 'b'];
50
+ const labels = { r: 'Red ', g: 'Green', b: 'Blue ' };
51
+ channels.forEach(ch => {
52
+ const val = this.rgb[ch];
53
+ const isActive = this.activeChannel === ch;
54
+ // Render slider track
55
+ // Width 20 chars for 0-255
56
+ const width = 20;
57
+ const pos = Math.floor((val / 255) * width);
58
+ // Wait, '⚪' might be wide. using simpler char if needed. 'O' or ANSI reverse space.
59
+ // Let's use standard chars.
60
+ const trackSimple = '━'.repeat(pos) + (isActive ? '●' : '○') + '─'.repeat(width - pos);
61
+ let line = `${labels[ch]}: ${val.toString().padStart(3)} [${trackSimple}]`;
62
+ if (isActive) {
63
+ line = `${theme_1.theme.main}${ansi_1.ANSI.REVERSE} ${line} ${ansi_1.ANSI.RESET}`;
64
+ }
65
+ else {
66
+ line = ` ${line} `;
67
+ }
68
+ output += line + '\n';
69
+ });
70
+ output += `\n${theme_1.theme.muted}Arrows: Adjust/Switch | Shift+Arrows: Adjust fast | Enter: Submit${ansi_1.ANSI.RESET}`;
71
+ this.renderFrame(output);
72
+ }
73
+ handleInput(char, _key) {
74
+ const isUp = this.isUp(char);
75
+ const isDown = this.isDown(char);
76
+ const isLeft = this.isLeft(char);
77
+ const isRight = this.isRight(char);
78
+ // Detect Shift key for faster movement (generic check if possible, or mapping specific codes)
79
+ // \x1b[1;2C is Shift+Right usually. \x1b[1;2D is Shift+Left.
80
+ // It varies by terminal.
81
+ const isShiftRight = char === '\x1b[1;2C';
82
+ const isShiftLeft = char === '\x1b[1;2D';
83
+ // Channel Switching (Up/Down)
84
+ if (isUp) {
85
+ if (this.activeChannel === 'g')
86
+ this.activeChannel = 'r';
87
+ else if (this.activeChannel === 'b')
88
+ this.activeChannel = 'g';
89
+ this.render(false);
90
+ return;
91
+ }
92
+ if (isDown) {
93
+ if (this.activeChannel === 'r')
94
+ this.activeChannel = 'g';
95
+ else if (this.activeChannel === 'g')
96
+ this.activeChannel = 'b';
97
+ this.render(false);
98
+ return;
99
+ }
100
+ // Value Adjustment (Left/Right)
101
+ if (isLeft || isRight || isShiftLeft || isShiftRight) {
102
+ let change = 0;
103
+ if (isRight)
104
+ change = 1;
105
+ if (isLeft)
106
+ change = -1;
107
+ if (isShiftRight)
108
+ change = 10;
109
+ if (isShiftLeft)
110
+ change = -10;
111
+ const val = this.rgb[this.activeChannel] + change;
112
+ this.rgb[this.activeChannel] = Math.max(0, Math.min(255, val));
113
+ this.render(false);
114
+ return;
115
+ }
116
+ // Tab to cycle channels
117
+ if (char === '\t') {
118
+ if (this.activeChannel === 'r')
119
+ this.activeChannel = 'g';
120
+ else if (this.activeChannel === 'g')
121
+ this.activeChannel = 'b';
122
+ else
123
+ this.activeChannel = 'r';
124
+ this.render(false);
125
+ return;
126
+ }
127
+ if (char === '\r' || char === '\n') {
128
+ this.submit(this.rgbToHex(this.rgb.r, this.rgb.g, this.rgb.b));
129
+ return;
130
+ }
131
+ }
132
+ handleMouse(event) {
133
+ if (event.action === 'scroll') {
134
+ // On scroll, adjust the currently active channel's value.
135
+ // If Ctrl is held, adjust by a larger step (fast adjust).
136
+ const fast = !!event.ctrl;
137
+ const step = fast ? 10 : 1;
138
+ const change = event.scroll === 'up' ? step : (event.scroll === 'down' ? -step : 0);
139
+ if (change !== 0) {
140
+ const val = this.rgb[this.activeChannel] + change;
141
+ this.rgb[this.activeChannel] = Math.max(0, Math.min(255, val));
142
+ this.render(false);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ exports.ColorPrompt = ColorPrompt;
@@ -2,7 +2,7 @@ import { Prompt } from '../base';
2
2
  import { ConfirmOptions, MouseEvent } from '../types';
3
3
  export declare class ConfirmPrompt extends Prompt<boolean, ConfirmOptions> {
4
4
  constructor(options: ConfirmOptions);
5
- protected render(firstRender: boolean): void;
5
+ protected render(_firstRender: boolean): void;
6
6
  protected handleInput(char: string): void;
7
7
  protected handleMouse(event: MouseEvent): void;
8
8
  }
@@ -10,7 +10,7 @@ class ConfirmPrompt extends base_1.Prompt {
10
10
  super(options);
11
11
  this.value = options.initial ?? true;
12
12
  }
13
- render(firstRender) {
13
+ render(_firstRender) {
14
14
  // Prepare content
15
15
  const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
16
16
  let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET} `;
@@ -0,0 +1,13 @@
1
+ import { Prompt } from '../base';
2
+ import { CronOptions, MouseEvent } from '../types';
3
+ export declare class CronPrompt extends Prompt<string, CronOptions> {
4
+ private fields;
5
+ private activeField;
6
+ private buffer;
7
+ constructor(options: CronOptions);
8
+ private get currentConfig();
9
+ protected render(_firstRender: boolean): void;
10
+ private validateCurrentField;
11
+ protected handleInput(char: string): void;
12
+ protected handleMouse(event: MouseEvent): void;
13
+ }
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CronPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ const FIELDS = [
8
+ { label: 'Minute', min: 0, max: 59 },
9
+ { label: 'Hour', min: 0, max: 23 },
10
+ { label: 'Day', min: 1, max: 31 },
11
+ { label: 'Month', min: 1, max: 12 },
12
+ { label: 'Weekday', min: 0, max: 6 }
13
+ ];
14
+ class CronPrompt extends base_1.Prompt {
15
+ constructor(options) {
16
+ super(options);
17
+ this.fields = ['*', '*', '*', '*', '*'];
18
+ this.activeField = 0;
19
+ this.buffer = '';
20
+ if (options.initial) {
21
+ const parts = options.initial.split(' ');
22
+ if (parts.length === 5) {
23
+ this.fields = parts;
24
+ }
25
+ }
26
+ }
27
+ get currentConfig() {
28
+ return FIELDS[this.activeField];
29
+ }
30
+ render(_firstRender) {
31
+ let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
32
+ // Render fields
33
+ let fieldsStr = '';
34
+ this.fields.forEach((val, index) => {
35
+ const isSelected = index === this.activeField;
36
+ const displayVal = val.padStart(2, ' ');
37
+ if (isSelected) {
38
+ fieldsStr += `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${displayVal}${ansi_1.ANSI.RESET} `;
39
+ }
40
+ else {
41
+ fieldsStr += `${ansi_1.ANSI.DIM}${displayVal}${ansi_1.ANSI.RESET} `;
42
+ }
43
+ });
44
+ output += ` ${fieldsStr}\n`;
45
+ // Render Label and Hints
46
+ const config = this.currentConfig;
47
+ output += ` ${theme_1.theme.muted}${config.label} (${config.min}-${config.max})${ansi_1.ANSI.RESET}\n`;
48
+ output += ` ${ansi_1.ANSI.DIM}(Arrows: Adjust | Space: Toggle * | 0-9: Type | Tab: Next)${ansi_1.ANSI.RESET}`;
49
+ this.renderFrame(output);
50
+ }
51
+ validateCurrentField() {
52
+ const config = this.currentConfig;
53
+ const val = this.fields[this.activeField];
54
+ if (val === '*')
55
+ return;
56
+ let num = parseInt(val);
57
+ if (isNaN(num)) {
58
+ this.fields[this.activeField] = config.min.toString();
59
+ }
60
+ else {
61
+ num = Math.max(config.min, Math.min(num, config.max));
62
+ this.fields[this.activeField] = num.toString();
63
+ }
64
+ }
65
+ handleInput(char) {
66
+ if (char === '\r' || char === '\n') {
67
+ this.validateCurrentField();
68
+ this.submit(this.fields.join(' '));
69
+ return;
70
+ }
71
+ if (char === '\t' || this.isRight(char)) {
72
+ this.validateCurrentField();
73
+ this.activeField = (this.activeField + 1) % 5;
74
+ this.buffer = '';
75
+ this.render(false);
76
+ return;
77
+ }
78
+ // Shift+Tab detection is often '\x1b[Z'
79
+ if (char === '\x1b[Z' || this.isLeft(char)) {
80
+ this.validateCurrentField();
81
+ this.activeField = (this.activeField - 1 + 5) % 5;
82
+ this.buffer = '';
83
+ this.render(false);
84
+ return;
85
+ }
86
+ const config = this.currentConfig;
87
+ const currentVal = this.fields[this.activeField];
88
+ let numVal = parseInt(currentVal);
89
+ if (this.isUp(char)) {
90
+ if (currentVal === '*') {
91
+ this.fields[this.activeField] = config.min.toString();
92
+ }
93
+ else if (!isNaN(numVal)) {
94
+ numVal++;
95
+ if (numVal > config.max)
96
+ numVal = config.min;
97
+ this.fields[this.activeField] = numVal.toString();
98
+ }
99
+ this.buffer = '';
100
+ this.render(false);
101
+ return;
102
+ }
103
+ if (this.isDown(char)) {
104
+ if (currentVal === '*') {
105
+ this.fields[this.activeField] = config.max.toString();
106
+ }
107
+ else if (!isNaN(numVal)) {
108
+ numVal--;
109
+ if (numVal < config.min)
110
+ numVal = config.max;
111
+ this.fields[this.activeField] = numVal.toString();
112
+ }
113
+ this.buffer = '';
114
+ this.render(false);
115
+ return;
116
+ }
117
+ if (char === ' ' || char.toLowerCase() === 'x') {
118
+ this.fields[this.activeField] = '*';
119
+ this.buffer = '';
120
+ this.render(false);
121
+ return;
122
+ }
123
+ // Direct Input
124
+ if (/^[0-9]$/.test(char)) {
125
+ const nextBuffer = this.buffer + char;
126
+ const nextNum = parseInt(nextBuffer);
127
+ // Check if valid
128
+ if (!isNaN(nextNum) && nextNum <= config.max) {
129
+ this.buffer = nextBuffer;
130
+ if (nextNum >= 0) { // Should be positive
131
+ this.fields[this.activeField] = nextNum.toString();
132
+ }
133
+ }
134
+ else {
135
+ const freshNum = parseInt(char);
136
+ if (!isNaN(freshNum) && freshNum <= config.max) {
137
+ this.buffer = char;
138
+ this.fields[this.activeField] = freshNum.toString();
139
+ }
140
+ }
141
+ this.render(false);
142
+ }
143
+ }
144
+ handleMouse(event) {
145
+ if (event.action === 'scroll') {
146
+ const config = this.currentConfig;
147
+ const currentVal = this.fields[this.activeField];
148
+ let numVal = parseInt(currentVal);
149
+ if (event.scroll === 'up') { // Increase
150
+ if (currentVal === '*') {
151
+ this.fields[this.activeField] = config.min.toString();
152
+ }
153
+ else if (!isNaN(numVal)) {
154
+ numVal++;
155
+ if (numVal > config.max)
156
+ numVal = config.min;
157
+ this.fields[this.activeField] = numVal.toString();
158
+ }
159
+ }
160
+ else { // Decrease
161
+ if (currentVal === '*') {
162
+ this.fields[this.activeField] = config.max.toString();
163
+ }
164
+ else if (!isNaN(numVal)) {
165
+ numVal--;
166
+ if (numVal < config.min)
167
+ numVal = config.max;
168
+ this.fields[this.activeField] = numVal.toString();
169
+ }
170
+ }
171
+ this.buffer = '';
172
+ this.render(false);
173
+ }
174
+ }
175
+ }
176
+ exports.CronPrompt = CronPrompt;
@@ -5,7 +5,7 @@ export declare class DatePrompt extends Prompt<Date, DateOptions> {
5
5
  private errorMsg;
6
6
  private inputBuffer;
7
7
  constructor(options: DateOptions);
8
- protected render(firstRender: boolean): void;
8
+ protected render(_firstRender: boolean): void;
9
9
  protected handleInput(char: string): void;
10
10
  protected handleMouse(event: MouseEvent): void;
11
11
  private adjustDate;
@@ -14,7 +14,7 @@ class DatePrompt extends base_1.Prompt {
14
14
  this.inputBuffer = '';
15
15
  this.value = options.initial || new Date();
16
16
  }
17
- render(firstRender) {
17
+ render(_firstRender) {
18
18
  // Date formatting
19
19
  const y = this.value.getFullYear();
20
20
  const m = (this.value.getMonth() + 1).toString().padStart(2, '0');
@@ -52,15 +52,25 @@ class DatePrompt extends base_1.Prompt {
52
52
  this.submit(this.value);
53
53
  return;
54
54
  }
55
- if (this.isLeft(char)) { // Left
56
- this.selectedField = Math.max(0, this.selectedField - 1);
55
+ if (char === '\t' || this.isRight(char)) { // Right / Tab
56
+ if (char === '\t') {
57
+ this.selectedField = (this.selectedField + 1) % 5;
58
+ }
59
+ else {
60
+ this.selectedField = Math.min(4, this.selectedField + 1);
61
+ }
57
62
  this.inputBuffer = ''; // Reset buffer on move
58
63
  this.errorMsg = '';
59
64
  this.render(false);
60
65
  return;
61
66
  }
62
- if (this.isRight(char)) { // Right
63
- this.selectedField = Math.min(4, this.selectedField + 1);
67
+ if (char === '\x1b[Z' || this.isLeft(char)) { // Left / Shift+Tab
68
+ if (char === '\x1b[Z') {
69
+ this.selectedField = (this.selectedField - 1 + 5) % 5;
70
+ }
71
+ else {
72
+ this.selectedField = Math.max(0, this.selectedField - 1);
73
+ }
64
74
  this.inputBuffer = ''; // Reset buffer on move
65
75
  this.errorMsg = '';
66
76
  this.render(false);
@@ -60,7 +60,7 @@ class EditorPrompt extends base_1.Prompt {
60
60
  fs.unlinkSync(this.tempFilePath);
61
61
  }
62
62
  }
63
- catch (error) {
63
+ catch (_error) {
64
64
  // Ignore cleanup errors
65
65
  }
66
66
  }
@@ -168,7 +168,7 @@ class EditorPrompt extends base_1.Prompt {
168
168
  this.tempFilePath = null; // Mark as cleaned
169
169
  }
170
170
  }
171
- catch (e) {
171
+ catch (_e) {
172
172
  // Ignore read/delete errors
173
173
  }
174
174
  this.restoreMep();
@@ -1,12 +1,19 @@
1
1
  import { Prompt } from '../base';
2
2
  import { FileOptions } from '../types';
3
+ /**
4
+ * Implementation of FilePrompt with autocomplete.
5
+ */
3
6
  export declare class FilePrompt extends Prompt<string, FileOptions> {
4
7
  private input;
5
8
  private cursor;
6
9
  private suggestions;
7
10
  private selectedSuggestion;
8
11
  private errorMsg;
12
+ private lastLinesUp;
9
13
  constructor(options: FileOptions);
14
+ /**
15
+ * Updates the suggestions list based on the current input path.
16
+ */
10
17
  private updateSuggestions;
11
18
  protected render(firstRender: boolean): void;
12
19
  protected handleInput(char: string): void;
@@ -40,7 +40,9 @@ const theme_1 = require("../theme");
40
40
  const symbols_1 = require("../symbols");
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
- // --- Implementation: File Prompt ---
43
+ /**
44
+ * Implementation of FilePrompt with autocomplete.
45
+ */
44
46
  class FilePrompt extends base_1.Prompt {
45
47
  constructor(options) {
46
48
  super(options);
@@ -49,22 +51,29 @@ class FilePrompt extends base_1.Prompt {
49
51
  this.suggestions = [];
50
52
  this.selectedSuggestion = -1;
51
53
  this.errorMsg = '';
54
+ this.lastLinesUp = 0;
52
55
  this.input = options.basePath || '';
53
56
  this.cursor = this.input.length;
57
+ this.updateSuggestions();
54
58
  }
59
+ /**
60
+ * Updates the suggestions list based on the current input path.
61
+ */
55
62
  updateSuggestions() {
56
63
  try {
57
- const dir = path.dirname(this.input) || '.';
58
- const partial = path.basename(this.input);
64
+ // Determine the directory to scan and the partial file name
65
+ const isDirQuery = this.input.endsWith('/') || this.input.endsWith('\\');
66
+ const dir = isDirQuery ? this.input : (path.dirname(this.input) || '.');
67
+ const partial = isDirQuery ? '' : path.basename(this.input);
59
68
  if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
60
69
  const files = fs.readdirSync(dir);
61
70
  this.suggestions = files
62
- .filter(f => f.startsWith(partial))
71
+ .filter(f => f.toLowerCase().startsWith(partial.toLowerCase()))
63
72
  .filter(f => {
64
73
  const fullPath = path.join(dir, f);
65
- // Handle errors if file doesn't exist or permission denied
66
74
  try {
67
- const isDir = fs.statSync(fullPath).isDirectory();
75
+ const stats = fs.statSync(fullPath);
76
+ const isDir = stats.isDirectory();
68
77
  if (this.options.onlyDirectories && !isDir)
69
78
  return false;
70
79
  if (this.options.extensions && !isDir) {
@@ -72,17 +81,18 @@ class FilePrompt extends base_1.Prompt {
72
81
  }
73
82
  return true;
74
83
  }
75
- catch (e) {
84
+ catch (_e) {
76
85
  return false;
77
86
  }
78
87
  })
79
88
  .map(f => {
80
89
  const fullPath = path.join(dir, f);
81
90
  try {
91
+ // Append separator if the file is a directory
82
92
  if (fs.statSync(fullPath).isDirectory())
83
- return f + '/';
93
+ return f + path.sep;
84
94
  }
85
- catch (e) { /* ignore */ }
95
+ catch (_e) { /* ignore */ }
86
96
  return f;
87
97
  });
88
98
  }
@@ -90,18 +100,21 @@ class FilePrompt extends base_1.Prompt {
90
100
  this.suggestions = [];
91
101
  }
92
102
  }
93
- catch (e) {
103
+ catch (_e) {
94
104
  this.suggestions = [];
95
105
  }
96
106
  this.selectedSuggestion = -1;
97
107
  }
98
108
  render(firstRender) {
99
- // Construct string
109
+ // Restore cursor position to the bottom before renderFrame clears the area
110
+ if (!firstRender && this.lastLinesUp > 0) {
111
+ this.print(`\x1b[${this.lastLinesUp}B`);
112
+ this.lastLinesUp = 0;
113
+ }
100
114
  const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
101
115
  let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${this.input}`;
102
- // Suggestions
103
116
  if (this.suggestions.length > 0) {
104
- output += '\n'; // Separate input from suggestions
117
+ output += '\n';
105
118
  const maxShow = 5;
106
119
  const displayed = this.suggestions.slice(0, maxShow);
107
120
  displayed.forEach((s, i) => {
@@ -115,86 +128,69 @@ class FilePrompt extends base_1.Prompt {
115
128
  }
116
129
  });
117
130
  if (this.suggestions.length > maxShow) {
118
- output += `\n ...and ${this.suggestions.length - maxShow} more`;
131
+ output += `\n ${theme_1.theme.muted}...and ${this.suggestions.length - maxShow} more${ansi_1.ANSI.RESET}`;
119
132
  }
120
133
  }
121
134
  this.renderFrame(output);
122
- this.print(ansi_1.ANSI.SHOW_CURSOR);
123
- // Restore Cursor Logic
124
- // We need to move up to the input line if we printed suggestions.
125
- // The input line is always the first line (index 0).
126
- // So we move up by (totalLines - 1).
127
- const totalLines = this.lastRenderHeight; // renderFrame sets this
135
+ // Move cursor back up to the input line
136
+ const totalLines = this.lastRenderHeight;
128
137
  if (totalLines > 1) {
129
- this.print(`\x1b[${totalLines - 1}A`);
138
+ this.lastLinesUp = totalLines - 1;
139
+ this.print(`\x1b[${this.lastLinesUp}A`);
130
140
  }
131
- // Move right
141
+ // Calculate horizontal cursor position on the input line
132
142
  const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
133
143
  const prefixLen = this.stripAnsi(prefix).length;
134
144
  const targetCol = prefixLen + this.input.length;
135
145
  this.print(ansi_1.ANSI.CURSOR_LEFT);
136
146
  if (targetCol > 0)
137
147
  this.print(`\x1b[${targetCol}C`);
148
+ this.print(ansi_1.ANSI.SHOW_CURSOR);
138
149
  }
139
150
  handleInput(char) {
140
- if (char === '\t') { // Tab
141
- if (this.suggestions.length === 1) {
142
- const dir = path.dirname(this.input);
143
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[0]);
151
+ if (char === '\t') {
152
+ if (this.suggestions.length > 0) {
153
+ // Use the selected suggestion or the first one available
154
+ const idx = this.selectedSuggestion === -1 ? 0 : this.selectedSuggestion;
155
+ const suggestion = this.suggestions[idx];
156
+ const isDir = this.input.endsWith('/') || this.input.endsWith('\\');
157
+ const dir = isDir ? this.input : path.dirname(this.input);
158
+ // Construct the new path accurately
159
+ const baseDir = (dir === '.' && !this.input.startsWith('.')) ? '' : dir;
160
+ this.input = path.join(baseDir, suggestion);
144
161
  this.cursor = this.input.length;
145
- this.suggestions = [];
146
- this.render(false);
147
- }
148
- else if (this.suggestions.length > 1) {
149
- // Cycle or show? For now cycle if selected
150
- if (this.selectedSuggestion !== -1) {
151
- const dir = path.dirname(this.input);
152
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
153
- this.cursor = this.input.length;
154
- this.suggestions = [];
155
- this.render(false);
156
- }
157
- else {
158
- // Just show suggestions (already done in render loop usually, but update logic ensures it)
159
- this.updateSuggestions();
160
- this.render(false);
161
- }
162
- }
163
- else {
162
+ // Immediately refresh suggestions for the new path
164
163
  this.updateSuggestions();
165
164
  this.render(false);
166
165
  }
167
166
  return;
168
167
  }
169
168
  if (char === '\r' || char === '\n') {
170
- if (this.selectedSuggestion !== -1) {
171
- const dir = path.dirname(this.input);
172
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
173
- this.cursor = this.input.length;
174
- this.suggestions = [];
175
- this.selectedSuggestion = -1;
176
- this.render(false);
177
- }
178
- else {
179
- this.submit(this.input);
169
+ // Move cursor to the bottom to ensure clean exit UI
170
+ if (this.lastLinesUp > 0) {
171
+ this.print(`\x1b[${this.lastLinesUp}B`);
172
+ this.lastLinesUp = 0;
180
173
  }
174
+ this.submit(this.input);
181
175
  return;
182
176
  }
183
- if (this.isDown(char)) { // Down
177
+ if (this.isDown(char)) {
184
178
  if (this.suggestions.length > 0) {
185
- this.selectedSuggestion = (this.selectedSuggestion + 1) % Math.min(this.suggestions.length, 5);
179
+ const count = Math.min(this.suggestions.length, 5);
180
+ this.selectedSuggestion = (this.selectedSuggestion + 1) % count;
186
181
  this.render(false);
187
182
  }
188
183
  return;
189
184
  }
190
- if (this.isUp(char)) { // Up
185
+ if (this.isUp(char)) {
191
186
  if (this.suggestions.length > 0) {
192
- this.selectedSuggestion = (this.selectedSuggestion - 1 + Math.min(this.suggestions.length, 5)) % Math.min(this.suggestions.length, 5);
187
+ const count = Math.min(this.suggestions.length, 5);
188
+ this.selectedSuggestion = (this.selectedSuggestion - 1 + count) % count;
193
189
  this.render(false);
194
190
  }
195
191
  return;
196
192
  }
197
- if (char === '\u0008' || char === '\x7f') { // Backspace
193
+ if (char === '\u0008' || char === '\x7f') {
198
194
  if (this.input.length > 0) {
199
195
  this.input = this.input.slice(0, -1);
200
196
  this.updateSuggestions();
@@ -0,0 +1,17 @@
1
+ import { Prompt } from '../base';
2
+ import { FormOptions, MouseEvent } from '../types';
3
+ export declare class FormPrompt extends Prompt<Record<string, string>, FormOptions> {
4
+ private values;
5
+ private activeIndex;
6
+ private fieldErrors;
7
+ private globalError;
8
+ private cursor;
9
+ private lastLinesUp;
10
+ constructor(options: FormOptions);
11
+ protected render(firstRender: boolean): void;
12
+ protected handleInput(char: string, key: Buffer): void;
13
+ protected handleMouse(event: MouseEvent): void;
14
+ private moveFocus;
15
+ private validateCurrentField;
16
+ private submitForm;
17
+ }