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.
- package/README.md +173 -6
- package/dist/ansi.d.ts +1 -0
- package/dist/ansi.js +1 -0
- package/dist/base.d.ts +1 -1
- package/dist/base.js +1 -10
- package/dist/core.d.ts +23 -1
- package/dist/core.js +60 -0
- package/dist/highlight.d.ts +1 -0
- package/dist/highlight.js +40 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/input.js +26 -14
- package/dist/prompts/autocomplete.d.ts +1 -1
- package/dist/prompts/autocomplete.js +2 -7
- package/dist/prompts/calendar.d.ts +20 -0
- package/dist/prompts/calendar.js +329 -0
- package/dist/prompts/checkbox.d.ts +1 -1
- package/dist/prompts/checkbox.js +38 -8
- package/dist/prompts/code.d.ts +17 -0
- package/dist/prompts/code.js +210 -0
- package/dist/prompts/color.d.ts +14 -0
- package/dist/prompts/color.js +147 -0
- package/dist/prompts/confirm.d.ts +1 -1
- package/dist/prompts/confirm.js +1 -1
- package/dist/prompts/cron.d.ts +13 -0
- package/dist/prompts/cron.js +176 -0
- package/dist/prompts/date.d.ts +1 -1
- package/dist/prompts/date.js +15 -5
- package/dist/prompts/editor.js +2 -2
- package/dist/prompts/file.d.ts +7 -0
- package/dist/prompts/file.js +56 -60
- package/dist/prompts/form.d.ts +17 -0
- package/dist/prompts/form.js +225 -0
- package/dist/prompts/grid.d.ts +14 -0
- package/dist/prompts/grid.js +178 -0
- package/dist/prompts/keypress.d.ts +2 -2
- package/dist/prompts/keypress.js +2 -2
- package/dist/prompts/list.d.ts +1 -1
- package/dist/prompts/list.js +42 -22
- package/dist/prompts/multi-select.d.ts +1 -1
- package/dist/prompts/multi-select.js +39 -4
- package/dist/prompts/number.d.ts +1 -1
- package/dist/prompts/number.js +2 -2
- package/dist/prompts/range.d.ts +9 -0
- package/dist/prompts/range.js +140 -0
- package/dist/prompts/rating.d.ts +1 -1
- package/dist/prompts/rating.js +1 -1
- package/dist/prompts/select.d.ts +1 -1
- package/dist/prompts/select.js +1 -1
- package/dist/prompts/slider.d.ts +1 -1
- package/dist/prompts/slider.js +1 -1
- package/dist/prompts/snippet.d.ts +18 -0
- package/dist/prompts/snippet.js +203 -0
- package/dist/prompts/sort.d.ts +1 -1
- package/dist/prompts/sort.js +1 -4
- package/dist/prompts/spam.d.ts +17 -0
- package/dist/prompts/spam.js +62 -0
- package/dist/prompts/table.d.ts +1 -1
- package/dist/prompts/table.js +1 -1
- package/dist/prompts/text.d.ts +1 -0
- package/dist/prompts/text.js +13 -31
- package/dist/prompts/toggle.d.ts +1 -1
- package/dist/prompts/toggle.js +1 -1
- package/dist/prompts/transfer.d.ts +18 -0
- package/dist/prompts/transfer.js +203 -0
- package/dist/prompts/tree-select.d.ts +32 -0
- package/dist/prompts/tree-select.js +277 -0
- package/dist/prompts/tree.d.ts +3 -3
- package/dist/prompts/tree.js +27 -19
- package/dist/prompts/wait.d.ts +18 -0
- package/dist/prompts/wait.js +62 -0
- package/dist/types.d.ts +84 -0
- package/dist/utils.js +1 -1
- package/example.ts +150 -15
- 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(
|
|
5
|
+
protected render(_firstRender: boolean): void;
|
|
6
6
|
protected handleInput(char: string): void;
|
|
7
7
|
protected handleMouse(event: MouseEvent): void;
|
|
8
8
|
}
|
package/dist/prompts/confirm.js
CHANGED
|
@@ -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(
|
|
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;
|
package/dist/prompts/date.d.ts
CHANGED
|
@@ -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(
|
|
8
|
+
protected render(_firstRender: boolean): void;
|
|
9
9
|
protected handleInput(char: string): void;
|
|
10
10
|
protected handleMouse(event: MouseEvent): void;
|
|
11
11
|
private adjustDate;
|
package/dist/prompts/date.js
CHANGED
|
@@ -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(
|
|
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.
|
|
56
|
-
|
|
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.
|
|
63
|
-
|
|
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);
|
package/dist/prompts/editor.js
CHANGED
|
@@ -60,7 +60,7 @@ class EditorPrompt extends base_1.Prompt {
|
|
|
60
60
|
fs.unlinkSync(this.tempFilePath);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
catch (
|
|
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 (
|
|
171
|
+
catch (_e) {
|
|
172
172
|
// Ignore read/delete errors
|
|
173
173
|
}
|
|
174
174
|
this.restoreMep();
|
package/dist/prompts/file.d.ts
CHANGED
|
@@ -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;
|
package/dist/prompts/file.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
58
|
-
const
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
103
|
+
catch (_e) {
|
|
94
104
|
this.suggestions = [];
|
|
95
105
|
}
|
|
96
106
|
this.selectedSuggestion = -1;
|
|
97
107
|
}
|
|
98
108
|
render(firstRender) {
|
|
99
|
-
//
|
|
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';
|
|
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
|
-
|
|
123
|
-
|
|
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.
|
|
138
|
+
this.lastLinesUp = totalLines - 1;
|
|
139
|
+
this.print(`\x1b[${this.lastLinesUp}A`);
|
|
130
140
|
}
|
|
131
|
-
//
|
|
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') {
|
|
141
|
-
if (this.suggestions.length
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
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)) {
|
|
177
|
+
if (this.isDown(char)) {
|
|
184
178
|
if (this.suggestions.length > 0) {
|
|
185
|
-
|
|
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)) {
|
|
185
|
+
if (this.isUp(char)) {
|
|
191
186
|
if (this.suggestions.length > 0) {
|
|
192
|
-
|
|
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') {
|
|
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
|
+
}
|