mepcli 0.5.0 → 0.5.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 +12 -3
- package/dist/core.d.ts +4 -1
- package/dist/core.js +12 -0
- package/dist/prompts/editor.d.ts +14 -0
- package/dist/prompts/editor.js +207 -0
- package/dist/prompts/keypress.d.ts +7 -0
- package/dist/prompts/keypress.js +57 -0
- package/dist/prompts/text.js +5 -5
- package/dist/prompts/tree.d.ts +20 -0
- package/dist/prompts/tree.js +223 -0
- package/dist/types.d.ts +21 -0
- package/dist/utils.js +5 -1
- package/example.ts +70 -19
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Zero Dependency:** Keeps your project clean and fast.
|
|
8
|
-
- **Comprehensive Prompts:** Includes `text`, `password`, `select`, `checkbox`, `confirm`, `number`, `toggle`, `list`, `slider`, `date`, `file`, `multiSelect`, `autocomplete`, `sort`, `table`, and `
|
|
8
|
+
- **Comprehensive Prompts:** Includes `text`, `password`, `select`, `checkbox`, `confirm`, `number`, `toggle`, `list`, `slider`, `date`, `file`, `multiSelect`, `autocomplete`, `sort`, `table`, `rating`, `editor`, `tree`, and `keypress`.
|
|
9
9
|
- **Mouse Support:** Built-in support for mouse interaction (SGR 1006 protocol). Scroll to navigate lists or change values; click to select.
|
|
10
10
|
- **Responsive Input:** Supports cursor movement (Left/Right) and character insertion/deletion in text-based prompts.
|
|
11
11
|
- **Validation:** Built-in support for input validation (sync and async) with custom error messages.
|
|
@@ -104,7 +104,13 @@ async function main() {
|
|
|
104
104
|
]
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
// Editor (External text editor)
|
|
108
|
+
const bio = await MepCLI.editor({
|
|
109
|
+
message: "Write your biography:",
|
|
110
|
+
extension: ".md"
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log({ name, age, newsletter, lang, tools, stars, city, priorities, user, bio });
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
main();
|
|
@@ -130,6 +136,9 @@ main();
|
|
|
130
136
|
* `autocomplete(options)` - Searchable selection with async suggestions.
|
|
131
137
|
* `sort(options)` - Reorder a list of items.
|
|
132
138
|
* `table(options)` - Display data in columns and select rows.
|
|
139
|
+
* `tree(options)` - Navigate and select from a hierarchical tree structure.
|
|
140
|
+
* `keypress(options)` - Wait for a specific key press or any key.
|
|
141
|
+
* `editor(options)` - Launch an external editor (Vim, Nano, Notepad, etc.) to capture multi-line content.
|
|
133
142
|
* `spinner(message)` - Returns a `Spinner` instance for manual control (`start`, `stop`, `update`, `success`, `error`).
|
|
134
143
|
|
|
135
144
|
## Mouse Support
|
|
@@ -137,7 +146,7 @@ main();
|
|
|
137
146
|
MepCLI automatically detects modern terminals and enables **Mouse Tracking** (using SGR 1006 protocol).
|
|
138
147
|
|
|
139
148
|
* **Scrolling:**
|
|
140
|
-
* `select`, `multiSelect`, `checkbox`, `autocomplete`, `table`: Scroll to navigate the list.
|
|
149
|
+
* `select`, `multiSelect`, `checkbox`, `autocomplete`, `table`, `tree`: Scroll to navigate the list.
|
|
141
150
|
* `number`, `slider`, `rating`, `date`: Scroll to increment/decrement values or fields.
|
|
142
151
|
* `sort`: Scroll to navigate or reorder items (when grabbed).
|
|
143
152
|
* `toggle`, `confirm`: Scroll to toggle the state.
|
package/dist/core.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TextOptions, SelectOptions, ConfirmOptions, CheckboxOptions, ThemeConfig, NumberOptions, ToggleOptions, ListOptions, SliderOptions, DateOptions, FileOptions, MultiSelectOptions, RatingOptions, AutocompleteOptions, SortOptions, TableOptions } from './types';
|
|
1
|
+
import { TextOptions, SelectOptions, ConfirmOptions, CheckboxOptions, ThemeConfig, NumberOptions, ToggleOptions, ListOptions, SliderOptions, DateOptions, FileOptions, MultiSelectOptions, RatingOptions, AutocompleteOptions, SortOptions, TableOptions, EditorOptions, TreeOptions, KeypressOptions } from './types';
|
|
2
2
|
import { Spinner } from './spinner';
|
|
3
3
|
/**
|
|
4
4
|
* Public Facade for MepCLI
|
|
@@ -25,4 +25,7 @@ export declare class MepCLI {
|
|
|
25
25
|
static autocomplete<const V>(options: AutocompleteOptions<V>): Promise<V>;
|
|
26
26
|
static sort(options: SortOptions): Promise<string[]>;
|
|
27
27
|
static table<const V>(options: TableOptions<V>): Promise<V>;
|
|
28
|
+
static editor(options: EditorOptions): Promise<string>;
|
|
29
|
+
static tree<const V>(options: TreeOptions<V>): Promise<V>;
|
|
30
|
+
static keypress(options: KeypressOptions): Promise<string>;
|
|
28
31
|
}
|
package/dist/core.js
CHANGED
|
@@ -18,6 +18,9 @@ const rating_1 = require("./prompts/rating");
|
|
|
18
18
|
const autocomplete_1 = require("./prompts/autocomplete");
|
|
19
19
|
const sort_1 = require("./prompts/sort");
|
|
20
20
|
const table_1 = require("./prompts/table");
|
|
21
|
+
const editor_1 = require("./prompts/editor");
|
|
22
|
+
const tree_1 = require("./prompts/tree");
|
|
23
|
+
const keypress_1 = require("./prompts/keypress");
|
|
21
24
|
/**
|
|
22
25
|
* Public Facade for MepCLI
|
|
23
26
|
*/
|
|
@@ -76,6 +79,15 @@ class MepCLI {
|
|
|
76
79
|
static table(options) {
|
|
77
80
|
return new table_1.TablePrompt(options).run();
|
|
78
81
|
}
|
|
82
|
+
static editor(options) {
|
|
83
|
+
return new editor_1.EditorPrompt(options).run();
|
|
84
|
+
}
|
|
85
|
+
static tree(options) {
|
|
86
|
+
return new tree_1.TreePrompt(options).run();
|
|
87
|
+
}
|
|
88
|
+
static keypress(options) {
|
|
89
|
+
return new keypress_1.KeypressPrompt(options).run();
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
exports.MepCLI = MepCLI;
|
|
81
93
|
MepCLI.theme = theme_1.theme;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { EditorOptions } from '../types';
|
|
3
|
+
export declare class EditorPrompt extends Prompt<string, EditorOptions> {
|
|
4
|
+
private errorMsg;
|
|
5
|
+
private status;
|
|
6
|
+
private tempFilePath;
|
|
7
|
+
constructor(options: EditorOptions);
|
|
8
|
+
protected cleanup(): void;
|
|
9
|
+
protected render(firstRender: boolean): void;
|
|
10
|
+
protected handleInput(char: string): void;
|
|
11
|
+
private resolveEditor;
|
|
12
|
+
private spawnEditor;
|
|
13
|
+
private restoreMep;
|
|
14
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EditorPrompt = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const base_1 = require("../base");
|
|
42
|
+
const theme_1 = require("../theme");
|
|
43
|
+
const symbols_1 = require("../symbols");
|
|
44
|
+
const ansi_1 = require("../ansi");
|
|
45
|
+
class EditorPrompt extends base_1.Prompt {
|
|
46
|
+
constructor(options) {
|
|
47
|
+
super(options);
|
|
48
|
+
this.errorMsg = '';
|
|
49
|
+
this.status = 'pending';
|
|
50
|
+
this.tempFilePath = null;
|
|
51
|
+
// Default waitUserInput to true if not specified
|
|
52
|
+
if (this.options.waitUserInput === undefined) {
|
|
53
|
+
this.options.waitUserInput = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
cleanup() {
|
|
57
|
+
if (this.tempFilePath) {
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(this.tempFilePath)) {
|
|
60
|
+
fs.unlinkSync(this.tempFilePath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Ignore cleanup errors
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
super.cleanup();
|
|
68
|
+
}
|
|
69
|
+
render(firstRender) {
|
|
70
|
+
if (this.status === 'editing') {
|
|
71
|
+
return; // Don't render while editor is open (stdio inherited)
|
|
72
|
+
}
|
|
73
|
+
const icon = this.status === 'done' ? theme_1.theme.success + symbols_1.symbols.tick : theme_1.theme.main + '?';
|
|
74
|
+
const message = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`;
|
|
75
|
+
const hint = this.options.waitUserInput
|
|
76
|
+
? ` ${theme_1.theme.muted}[Press <Enter> to launch editor]${ansi_1.ANSI.RESET}`
|
|
77
|
+
: ` ${theme_1.theme.muted}[Launching editor...]${ansi_1.ANSI.RESET}`;
|
|
78
|
+
let output = `${icon} ${ansi_1.ANSI.BOLD}${message}${ansi_1.ANSI.RESET}${hint}`;
|
|
79
|
+
if (this.errorMsg) {
|
|
80
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
81
|
+
}
|
|
82
|
+
this.renderFrame(output);
|
|
83
|
+
// Auto-launch handling
|
|
84
|
+
if (firstRender && this.options.waitUserInput === false) {
|
|
85
|
+
// We need to delay slightly to ensure the render frame is flushed
|
|
86
|
+
// and raw mode setup is complete from .run()
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
this.spawnEditor();
|
|
89
|
+
}, 50);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
handleInput(char) {
|
|
93
|
+
if (this.status !== 'pending')
|
|
94
|
+
return;
|
|
95
|
+
// Enter
|
|
96
|
+
if (char === '\r' || char === '\n') {
|
|
97
|
+
this.spawnEditor();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
resolveEditor() {
|
|
101
|
+
// 1. Env vars
|
|
102
|
+
const envEditor = process.env.VISUAL || process.env.EDITOR;
|
|
103
|
+
if (envEditor) {
|
|
104
|
+
const parts = envEditor.split(' ');
|
|
105
|
+
return { cmd: parts[0], args: parts.slice(1) };
|
|
106
|
+
}
|
|
107
|
+
// 2. OS specific
|
|
108
|
+
if (process.platform === 'win32') {
|
|
109
|
+
// Priority: notepad -> code -> wordpad
|
|
110
|
+
return { cmd: 'notepad', args: [] };
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Unix/Linux/Mac
|
|
114
|
+
// Priority: vim -> nano -> vi
|
|
115
|
+
// We'll stick to 'vim' as the default safe bet if we can't detect.
|
|
116
|
+
// A more robust solution would check paths, but for now we assume 'vim'.
|
|
117
|
+
return { cmd: 'vim', args: [] };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
spawnEditor() {
|
|
121
|
+
this.status = 'editing';
|
|
122
|
+
// 1. Prepare Temp File
|
|
123
|
+
const ext = this.options.extension || '.txt';
|
|
124
|
+
// Ensure extension has dot
|
|
125
|
+
const safeExt = ext.startsWith('.') ? ext : '.' + ext;
|
|
126
|
+
const filename = `mep-editor-${Date.now()}-${Math.floor(Math.random() * 1000)}${safeExt}`;
|
|
127
|
+
this.tempFilePath = path.join(os.tmpdir(), filename);
|
|
128
|
+
const initialContent = this.options.initial || '';
|
|
129
|
+
try {
|
|
130
|
+
fs.writeFileSync(this.tempFilePath, initialContent, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
this.errorMsg = `Failed to create temp file: ${e.message}`;
|
|
134
|
+
this.status = 'pending';
|
|
135
|
+
this.render(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// 2. Resolve Editor
|
|
139
|
+
const { cmd, args } = this.resolveEditor();
|
|
140
|
+
const editorArgs = [...args, this.tempFilePath];
|
|
141
|
+
// 3. Pause Mep
|
|
142
|
+
// Temporarily disable mouse tracking if it was enabled
|
|
143
|
+
const shouldEnableMouse = this.options.mouse !== false && this.capabilities.hasMouse;
|
|
144
|
+
if (shouldEnableMouse) {
|
|
145
|
+
this.print(ansi_1.ANSI.DISABLE_MOUSE);
|
|
146
|
+
}
|
|
147
|
+
// Pause stdin and raw mode to allow child process to take over TTY
|
|
148
|
+
this.stdin.setRawMode(false);
|
|
149
|
+
this.stdin.pause();
|
|
150
|
+
// 4. Spawn
|
|
151
|
+
const child = (0, child_process_1.spawn)(cmd, editorArgs, {
|
|
152
|
+
stdio: 'inherit',
|
|
153
|
+
shell: true
|
|
154
|
+
});
|
|
155
|
+
child.on('error', (err) => {
|
|
156
|
+
this.restoreMep();
|
|
157
|
+
this.status = 'pending';
|
|
158
|
+
this.errorMsg = `Could not launch editor '${cmd}': ${err.message}`;
|
|
159
|
+
this.render(false);
|
|
160
|
+
});
|
|
161
|
+
child.on('exit', (code) => {
|
|
162
|
+
// 5. Read Result
|
|
163
|
+
let content = initialContent;
|
|
164
|
+
try {
|
|
165
|
+
if (this.tempFilePath && fs.existsSync(this.tempFilePath)) {
|
|
166
|
+
content = fs.readFileSync(this.tempFilePath, 'utf8');
|
|
167
|
+
fs.unlinkSync(this.tempFilePath); // Cleanup
|
|
168
|
+
this.tempFilePath = null; // Mark as cleaned
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
// Ignore read/delete errors
|
|
173
|
+
}
|
|
174
|
+
this.restoreMep();
|
|
175
|
+
if (code !== 0) {
|
|
176
|
+
this.status = 'pending';
|
|
177
|
+
this.errorMsg = `Editor exited with code ${code}`;
|
|
178
|
+
this.render(false);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Success
|
|
182
|
+
this.status = 'done';
|
|
183
|
+
// Trim trailing newline which editors often add
|
|
184
|
+
// We only trim the *last* newline added by the editor if it wasn't there?
|
|
185
|
+
// Usually editors ensure a final newline.
|
|
186
|
+
// If the user entered "abc", vim saves "abc\n". We probably want "abc".
|
|
187
|
+
if (content.endsWith('\n')) {
|
|
188
|
+
content = content.slice(0, -1);
|
|
189
|
+
}
|
|
190
|
+
if (content.endsWith('\r')) {
|
|
191
|
+
content = content.slice(0, -1);
|
|
192
|
+
}
|
|
193
|
+
this.submit(content);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
restoreMep() {
|
|
197
|
+
this.stdin.resume();
|
|
198
|
+
this.stdin.setRawMode(true);
|
|
199
|
+
// Re-enable mouse if it was enabled
|
|
200
|
+
const shouldEnableMouse = this.options.mouse !== false && this.capabilities.hasMouse;
|
|
201
|
+
if (shouldEnableMouse) {
|
|
202
|
+
this.print(ansi_1.ANSI.SET_ANY_EVENT_MOUSE + ansi_1.ANSI.SET_SGR_EXT_MODE_MOUSE);
|
|
203
|
+
}
|
|
204
|
+
this.print(ansi_1.ANSI.HIDE_CURSOR); // Ensure cursor is hidden again for Mep
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.EditorPrompt = EditorPrompt;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { KeypressOptions } from '../types';
|
|
3
|
+
export declare class KeypressPrompt extends Prompt<string, KeypressOptions> {
|
|
4
|
+
constructor(options: KeypressOptions);
|
|
5
|
+
protected render(firstRender: boolean): void;
|
|
6
|
+
protected handleInput(char: string, key: Buffer): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeypressPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
class KeypressPrompt extends base_1.Prompt {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
}
|
|
11
|
+
render(firstRender) {
|
|
12
|
+
let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`;
|
|
13
|
+
if (this.options.keys) {
|
|
14
|
+
const hint = this.options.keys.map(k => {
|
|
15
|
+
if (k === '\r' || k === '\n' || k === 'enter')
|
|
16
|
+
return 'enter';
|
|
17
|
+
if (k === ' ' || k === 'space')
|
|
18
|
+
return 'space';
|
|
19
|
+
return k;
|
|
20
|
+
}).join('/');
|
|
21
|
+
// Only show hint if it's short enough to be helpful, or always?
|
|
22
|
+
// Let's always show it if provided, or maybe just dimmed.
|
|
23
|
+
output += ` ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET}`;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
output += ` ${theme_1.theme.muted}(Press any key)${ansi_1.ANSI.RESET}`;
|
|
27
|
+
}
|
|
28
|
+
this.renderFrame(output);
|
|
29
|
+
}
|
|
30
|
+
handleInput(char, key) {
|
|
31
|
+
let keyName = char;
|
|
32
|
+
if (char === '\r' || char === '\n')
|
|
33
|
+
keyName = 'enter';
|
|
34
|
+
else if (char === ' ')
|
|
35
|
+
keyName = 'space';
|
|
36
|
+
else if (char === '\u001b')
|
|
37
|
+
keyName = 'escape';
|
|
38
|
+
else if (char === '\t')
|
|
39
|
+
keyName = 'tab';
|
|
40
|
+
// Handle backspace
|
|
41
|
+
else if (char === '\x7f' || char === '\b')
|
|
42
|
+
keyName = 'backspace';
|
|
43
|
+
// Check against whitelist
|
|
44
|
+
if (this.options.keys) {
|
|
45
|
+
const allowed = this.options.keys.map(k => k.toLowerCase());
|
|
46
|
+
// Check normalized name or exact char
|
|
47
|
+
if (!allowed.includes(keyName) && !allowed.includes(char)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (this.options.showInvisible) {
|
|
52
|
+
this.print(` ${theme_1.theme.success}${keyName}${ansi_1.ANSI.RESET}`);
|
|
53
|
+
}
|
|
54
|
+
this.submit(keyName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.KeypressPrompt = KeypressPrompt;
|
package/dist/prompts/text.js
CHANGED
|
@@ -52,22 +52,22 @@ class TextPrompt extends base_1.Prompt {
|
|
|
52
52
|
// This is tricky because segments might contain '\n'.
|
|
53
53
|
// safeSplit treats '\n' as a segment.
|
|
54
54
|
let cursorLineIndex = 0;
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const cursorSegmentIndexOnLine = 0;
|
|
56
|
+
const currentSegmentIndex = 0;
|
|
57
57
|
for (let i = 0; i < lines.length; i++) {
|
|
58
58
|
// How many segments in this line?
|
|
59
59
|
// We can't just use lines[i].length because that's chars.
|
|
60
60
|
// We need to split the line again or iterate segments.
|
|
61
61
|
// Iterating segments is safer.
|
|
62
62
|
// Let's assume we iterate global segments until we hit a newline segment
|
|
63
|
-
|
|
63
|
+
const lineSegmentsCount = 0;
|
|
64
64
|
// Since rawValue.split('\n') consumes the newlines, we need to account for them.
|
|
65
65
|
// Alternative: iterate this.segments
|
|
66
66
|
// Find where the cursor falls.
|
|
67
67
|
}
|
|
68
68
|
// Let's iterate segments to find cursor position (row, col)
|
|
69
69
|
cursorLineIndex = 0;
|
|
70
|
-
|
|
70
|
+
const colIndex = 0; // Visual column or char index?
|
|
71
71
|
// If we want visual cursor position, we need visual width of segments.
|
|
72
72
|
let visualColIndex = 0;
|
|
73
73
|
for (let i = 0; i < this.cursor; i++) {
|
|
@@ -90,7 +90,7 @@ class TextPrompt extends base_1.Prompt {
|
|
|
90
90
|
// Now prepare lines for display (scrolling/truncation)
|
|
91
91
|
// We need to reconstruct lines from segments to apply styling/truncation logic per line.
|
|
92
92
|
let currentLineSegments = [];
|
|
93
|
-
|
|
93
|
+
const processedLines = []; // Array of segment arrays
|
|
94
94
|
for (const seg of this.segments) {
|
|
95
95
|
if (seg === '\n') {
|
|
96
96
|
processedLines.push(currentLineSegments);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { TreeOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class TreePrompt<V> extends Prompt<V, TreeOptions<V>> {
|
|
4
|
+
private cursor;
|
|
5
|
+
private expandedNodes;
|
|
6
|
+
private flatList;
|
|
7
|
+
private scrollTop;
|
|
8
|
+
private readonly pageSize;
|
|
9
|
+
private readonly ICON_CLOSED;
|
|
10
|
+
private readonly ICON_OPEN;
|
|
11
|
+
private readonly ICON_LEAF;
|
|
12
|
+
constructor(options: TreeOptions<V>);
|
|
13
|
+
private expandPathTo;
|
|
14
|
+
private initializeExpanded;
|
|
15
|
+
private recalculateFlatList;
|
|
16
|
+
private traverse;
|
|
17
|
+
protected render(firstRender: boolean): void;
|
|
18
|
+
protected handleInput(char: string, key: Buffer): void;
|
|
19
|
+
protected handleMouse(event: MouseEvent): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TreePrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
const symbols_1 = require("../symbols");
|
|
8
|
+
class TreePrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.cursor = 0;
|
|
12
|
+
this.expandedNodes = new Set();
|
|
13
|
+
this.flatList = [];
|
|
14
|
+
this.scrollTop = 0;
|
|
15
|
+
this.pageSize = 15;
|
|
16
|
+
// Icons
|
|
17
|
+
this.ICON_CLOSED = symbols_1.symbols.pointer === '>' ? '+' : '▸';
|
|
18
|
+
this.ICON_OPEN = symbols_1.symbols.pointer === '>' ? '-' : '▾';
|
|
19
|
+
this.ICON_LEAF = symbols_1.symbols.pointer === '>' ? ' ' : ' '; // No specific icon for leaf, just indentation
|
|
20
|
+
this.initializeExpanded(this.options.data);
|
|
21
|
+
// Handle initial value
|
|
22
|
+
if (this.options.initial !== undefined) {
|
|
23
|
+
this.expandPathTo(this.options.initial);
|
|
24
|
+
}
|
|
25
|
+
this.recalculateFlatList();
|
|
26
|
+
if (this.options.initial !== undefined) {
|
|
27
|
+
const index = this.flatList.findIndex(item => item.node.value === this.options.initial);
|
|
28
|
+
if (index !== -1) {
|
|
29
|
+
this.cursor = index;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
expandPathTo(value) {
|
|
34
|
+
const find = (nodes) => {
|
|
35
|
+
for (const node of nodes) {
|
|
36
|
+
if (node.value === value)
|
|
37
|
+
return true;
|
|
38
|
+
if (node.children) {
|
|
39
|
+
if (find(node.children)) {
|
|
40
|
+
this.expandedNodes.add(node);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
find(this.options.data);
|
|
48
|
+
}
|
|
49
|
+
initializeExpanded(nodes) {
|
|
50
|
+
for (const node of nodes) {
|
|
51
|
+
if (node.expanded) {
|
|
52
|
+
this.expandedNodes.add(node);
|
|
53
|
+
}
|
|
54
|
+
if (node.children) {
|
|
55
|
+
this.initializeExpanded(node.children);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
recalculateFlatList() {
|
|
60
|
+
this.flatList = [];
|
|
61
|
+
this.traverse(this.options.data, 0, null);
|
|
62
|
+
// Adjust cursor if it went out of bounds (e.g. collapsing a folder above cursor)
|
|
63
|
+
if (this.cursor >= this.flatList.length) {
|
|
64
|
+
this.cursor = Math.max(0, this.flatList.length - 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
traverse(nodes, depth, parent) {
|
|
68
|
+
for (const node of nodes) {
|
|
69
|
+
this.flatList.push({
|
|
70
|
+
node,
|
|
71
|
+
depth,
|
|
72
|
+
parent
|
|
73
|
+
});
|
|
74
|
+
if (node.children && node.children.length > 0 && this.expandedNodes.has(node)) {
|
|
75
|
+
this.traverse(node.children, depth + 1, node);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
render(firstRender) {
|
|
80
|
+
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`;
|
|
81
|
+
if (this.flatList.length === 0) {
|
|
82
|
+
output += ` ${theme_1.theme.muted}No data${ansi_1.ANSI.RESET}`;
|
|
83
|
+
this.renderFrame(output);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Adjust Scroll
|
|
87
|
+
if (this.cursor < this.scrollTop) {
|
|
88
|
+
this.scrollTop = this.cursor;
|
|
89
|
+
}
|
|
90
|
+
else if (this.cursor >= this.scrollTop + this.pageSize) {
|
|
91
|
+
this.scrollTop = this.cursor - this.pageSize + 1;
|
|
92
|
+
}
|
|
93
|
+
const visible = this.flatList.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
94
|
+
visible.forEach((item, index) => {
|
|
95
|
+
const actualIndex = this.scrollTop + index;
|
|
96
|
+
const isSelected = actualIndex === this.cursor;
|
|
97
|
+
// Indentation
|
|
98
|
+
const indentSize = this.options.indent || 2;
|
|
99
|
+
const indentation = ' '.repeat(item.depth * indentSize);
|
|
100
|
+
// Pointer
|
|
101
|
+
let linePrefix = '';
|
|
102
|
+
if (isSelected) {
|
|
103
|
+
linePrefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
linePrefix = ' ';
|
|
107
|
+
}
|
|
108
|
+
// Folder Icon
|
|
109
|
+
let icon = ' '; // Default 2 spaces for alignment
|
|
110
|
+
const hasChildren = item.node.children && item.node.children.length > 0;
|
|
111
|
+
if (hasChildren) {
|
|
112
|
+
if (this.expandedNodes.has(item.node)) {
|
|
113
|
+
icon = `${this.ICON_OPEN} `;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
icon = `${this.ICON_CLOSED} `;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Title
|
|
120
|
+
let title = item.node.title;
|
|
121
|
+
if (item.node.disabled) {
|
|
122
|
+
title = `${theme_1.theme.muted}${title} (disabled)${ansi_1.ANSI.RESET}`;
|
|
123
|
+
}
|
|
124
|
+
// Compose line
|
|
125
|
+
let line = `${indentation}${icon}${title}`;
|
|
126
|
+
if (isSelected) {
|
|
127
|
+
line = `${theme_1.theme.main}${line}${ansi_1.ANSI.RESET}`;
|
|
128
|
+
}
|
|
129
|
+
output += linePrefix + line;
|
|
130
|
+
if (index < visible.length - 1)
|
|
131
|
+
output += '\n';
|
|
132
|
+
});
|
|
133
|
+
this.renderFrame(output);
|
|
134
|
+
}
|
|
135
|
+
handleInput(char, key) {
|
|
136
|
+
if (this.flatList.length === 0)
|
|
137
|
+
return;
|
|
138
|
+
// Navigation
|
|
139
|
+
if (this.isUp(char)) {
|
|
140
|
+
this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
|
|
141
|
+
this.render(false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (this.isDown(char)) {
|
|
145
|
+
this.cursor = (this.cursor + 1) % this.flatList.length;
|
|
146
|
+
this.render(false);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const currentItem = this.flatList[this.cursor];
|
|
150
|
+
const node = currentItem.node;
|
|
151
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
152
|
+
// Right: Expand or Go Down
|
|
153
|
+
if (this.isRight(char)) {
|
|
154
|
+
if (hasChildren) {
|
|
155
|
+
if (!this.expandedNodes.has(node)) {
|
|
156
|
+
this.expandedNodes.add(node);
|
|
157
|
+
this.recalculateFlatList();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Go to first child (next item in flat list)
|
|
161
|
+
if (this.cursor + 1 < this.flatList.length) {
|
|
162
|
+
this.cursor++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.render(false);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Left: Collapse or Go Up (Parent)
|
|
170
|
+
if (this.isLeft(char)) {
|
|
171
|
+
if (hasChildren && this.expandedNodes.has(node)) {
|
|
172
|
+
this.expandedNodes.delete(node);
|
|
173
|
+
this.recalculateFlatList();
|
|
174
|
+
this.render(false);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Go to parent
|
|
179
|
+
if (currentItem.parent) {
|
|
180
|
+
const parentIndex = this.flatList.findIndex(x => x.node === currentItem.parent);
|
|
181
|
+
if (parentIndex !== -1) {
|
|
182
|
+
this.cursor = parentIndex;
|
|
183
|
+
this.render(false);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Toggle (Space)
|
|
190
|
+
if (char === ' ') {
|
|
191
|
+
if (hasChildren) {
|
|
192
|
+
if (this.expandedNodes.has(node)) {
|
|
193
|
+
this.expandedNodes.delete(node);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.expandedNodes.add(node);
|
|
197
|
+
}
|
|
198
|
+
this.recalculateFlatList();
|
|
199
|
+
this.render(false);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Submit (Enter)
|
|
204
|
+
if (char === '\r' || char === '\n') {
|
|
205
|
+
if (!node.disabled) {
|
|
206
|
+
this.submit(node.value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
handleMouse(event) {
|
|
211
|
+
if (event.action === 'scroll') {
|
|
212
|
+
if (event.scroll === 'up') {
|
|
213
|
+
this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
|
|
214
|
+
this.render(false);
|
|
215
|
+
}
|
|
216
|
+
else if (event.scroll === 'down') {
|
|
217
|
+
this.cursor = (this.cursor + 1) % this.flatList.length;
|
|
218
|
+
this.render(false);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.TreePrompt = TreePrompt;
|
package/dist/types.d.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface AutocompleteOptions<V> extends BaseOptions {
|
|
|
100
100
|
export interface SortOptions extends BaseOptions {
|
|
101
101
|
items: string[];
|
|
102
102
|
}
|
|
103
|
+
export interface EditorOptions extends BaseOptions {
|
|
104
|
+
initial?: string;
|
|
105
|
+
extension?: string;
|
|
106
|
+
waitUserInput?: boolean;
|
|
107
|
+
}
|
|
103
108
|
export interface TableRow<V> {
|
|
104
109
|
value: V;
|
|
105
110
|
row: string[];
|
|
@@ -109,3 +114,19 @@ export interface TableOptions<V> extends BaseOptions {
|
|
|
109
114
|
data: TableRow<V>[];
|
|
110
115
|
rows?: number;
|
|
111
116
|
}
|
|
117
|
+
export interface TreeNode<V> {
|
|
118
|
+
title: string;
|
|
119
|
+
value: V;
|
|
120
|
+
children?: TreeNode<V>[];
|
|
121
|
+
expanded?: boolean;
|
|
122
|
+
disabled?: boolean;
|
|
123
|
+
}
|
|
124
|
+
export interface TreeOptions<V> extends BaseOptions {
|
|
125
|
+
data: TreeNode<V>[];
|
|
126
|
+
initial?: V;
|
|
127
|
+
indent?: number;
|
|
128
|
+
}
|
|
129
|
+
export interface KeypressOptions extends BaseOptions {
|
|
130
|
+
keys?: string[];
|
|
131
|
+
showInvisible?: boolean;
|
|
132
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -131,7 +131,11 @@ function stringWidth(str) {
|
|
|
131
131
|
continue;
|
|
132
132
|
}
|
|
133
133
|
if (inAnsi) {
|
|
134
|
-
if (
|
|
134
|
+
if (str[i] === '[') {
|
|
135
|
+
// Continue, this is the start of CSI
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if ((str[i] >= '@' && str[i] <= '~')) {
|
|
135
139
|
inAnsi = false;
|
|
136
140
|
}
|
|
137
141
|
continue;
|
package/example.ts
CHANGED
|
@@ -21,14 +21,14 @@ async function runComprehensiveDemo() {
|
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
-
console.log(`\n
|
|
24
|
+
console.log(`\n Text Result: Project name set to '${projectName}'`);
|
|
25
25
|
|
|
26
26
|
// --- 2. Password Prompt (Hidden input) ---
|
|
27
27
|
const apiKey = await MepCLI.password({
|
|
28
28
|
message: "Enter the project's external API key:",
|
|
29
29
|
placeholder: "Input will be hidden..."
|
|
30
30
|
});
|
|
31
|
-
console.log(`\n
|
|
31
|
+
console.log(`\n Password Result: API key entered (length: ${apiKey.length})`);
|
|
32
32
|
|
|
33
33
|
// --- 3. Select Prompt (Single choice, supports filtering/searching by typing) ---
|
|
34
34
|
const theme = await MepCLI.select({
|
|
@@ -42,7 +42,7 @@ async function runComprehensiveDemo() {
|
|
|
42
42
|
{ title: "Monokai Pro", value: "monokai" },
|
|
43
43
|
]
|
|
44
44
|
});
|
|
45
|
-
console.log(`\n
|
|
45
|
+
console.log(`\n Select Result: Chosen theme is: ${theme}`);
|
|
46
46
|
|
|
47
47
|
// --- 4. Checkbox Prompt (Multi-choice with Min/Max limits) ---
|
|
48
48
|
const buildTools = await MepCLI.checkbox({
|
|
@@ -56,7 +56,7 @@ async function runComprehensiveDemo() {
|
|
|
56
56
|
{ title: "esbuild", value: "esbuild" }
|
|
57
57
|
]
|
|
58
58
|
});
|
|
59
|
-
console.log(`\n
|
|
59
|
+
console.log(`\n Checkbox Result: Selected build tools: [${buildTools.join(', ')}]`);
|
|
60
60
|
|
|
61
61
|
// --- 5. Number Prompt (Numeric input, supports Min/Max and Up/Down arrow for Step) ---
|
|
62
62
|
const port = await MepCLI.number({
|
|
@@ -66,7 +66,7 @@ async function runComprehensiveDemo() {
|
|
|
66
66
|
max: 65535,
|
|
67
67
|
step: 100 // Increments/decrements by 100 with arrows
|
|
68
68
|
});
|
|
69
|
-
console.log(`\n
|
|
69
|
+
console.log(`\n Number Result: Server port: ${port}`);
|
|
70
70
|
|
|
71
71
|
// --- 6. Toggle Prompt (Boolean input, supports custom labels) ---
|
|
72
72
|
const isSecure = await MepCLI.toggle({
|
|
@@ -75,7 +75,7 @@ async function runComprehensiveDemo() {
|
|
|
75
75
|
activeText: "SECURE", // Custom 'on' label
|
|
76
76
|
inactiveText: "INSECURE" // Custom 'off' label
|
|
77
77
|
});
|
|
78
|
-
console.log(`\n
|
|
78
|
+
console.log(`\n Toggle Result: HTTPS enabled: ${isSecure}`);
|
|
79
79
|
|
|
80
80
|
// --- 7. List / Tags Input (New) ---
|
|
81
81
|
const keywords = await MepCLI.list({
|
|
@@ -83,7 +83,7 @@ async function runComprehensiveDemo() {
|
|
|
83
83
|
initial: ["cli", "mep"],
|
|
84
84
|
validate: (tags) => tags.length > 0 || "Please add at least one keyword."
|
|
85
85
|
});
|
|
86
|
-
console.log(`\n
|
|
86
|
+
console.log(`\n List Result: Keywords: [${keywords.join(', ')}]`);
|
|
87
87
|
|
|
88
88
|
// --- 8. Slider / Scale (New) ---
|
|
89
89
|
const brightness = await MepCLI.slider({
|
|
@@ -94,7 +94,7 @@ async function runComprehensiveDemo() {
|
|
|
94
94
|
step: 5,
|
|
95
95
|
unit: "%"
|
|
96
96
|
});
|
|
97
|
-
console.log(`\n
|
|
97
|
+
console.log(`\n Slider Result: Brightness: ${brightness}%`);
|
|
98
98
|
|
|
99
99
|
// --- 9. Rating Prompt (New) ---
|
|
100
100
|
const userRating = await MepCLI.rating({
|
|
@@ -103,7 +103,7 @@ async function runComprehensiveDemo() {
|
|
|
103
103
|
max: 5,
|
|
104
104
|
initial: 5
|
|
105
105
|
});
|
|
106
|
-
console.log(`\n
|
|
106
|
+
console.log(`\n Rating Result: You rated it: ${userRating}/5`);
|
|
107
107
|
|
|
108
108
|
// --- 10. Date / Time Picker (New) ---
|
|
109
109
|
// We capture 'now' once to ensure initial >= min
|
|
@@ -113,14 +113,14 @@ async function runComprehensiveDemo() {
|
|
|
113
113
|
initial: now,
|
|
114
114
|
min: now // Cannot be in the past
|
|
115
115
|
});
|
|
116
|
-
console.log(`\n
|
|
116
|
+
console.log(`\n Date Result: Release set for: ${releaseDate.toLocaleString()}`);
|
|
117
117
|
|
|
118
118
|
// --- 11. File Path Selector (New) ---
|
|
119
119
|
const configPath = await MepCLI.file({
|
|
120
120
|
message: "Select configuration file (Tab to autocomplete):",
|
|
121
121
|
basePath: process.cwd()
|
|
122
122
|
});
|
|
123
|
-
console.log(`\n
|
|
123
|
+
console.log(`\n File Result: Path: ${configPath}`);
|
|
124
124
|
|
|
125
125
|
// --- 12. Multi-Select Autocomplete (New) ---
|
|
126
126
|
const linters = await MepCLI.multiSelect({
|
|
@@ -135,7 +135,7 @@ async function runComprehensiveDemo() {
|
|
|
135
135
|
],
|
|
136
136
|
min: 1
|
|
137
137
|
});
|
|
138
|
-
console.log(`\n
|
|
138
|
+
console.log(`\n MultiSelect Result: Linters: [${linters.join(', ')}]`);
|
|
139
139
|
|
|
140
140
|
// --- 13. Autocomplete Prompt (New) ---
|
|
141
141
|
const city = await MepCLI.autocomplete({
|
|
@@ -155,14 +155,14 @@ async function runComprehensiveDemo() {
|
|
|
155
155
|
return cities.filter(c => c.title.toLowerCase().includes(query.toLowerCase()));
|
|
156
156
|
}
|
|
157
157
|
});
|
|
158
|
-
console.log(`\n
|
|
158
|
+
console.log(`\n Autocomplete Result: City code: ${city}`);
|
|
159
159
|
|
|
160
160
|
// --- 14. Sort Prompt (New) ---
|
|
161
161
|
const priorities = await MepCLI.sort({
|
|
162
162
|
message: "Rank your top priorities (Space to grab/drop, Arrows to move):",
|
|
163
163
|
items: ["Performance", "Security", "Features", "Usability", "Cost"]
|
|
164
164
|
});
|
|
165
|
-
console.log(`\n
|
|
165
|
+
console.log(`\n Sort Result: Priorities: [${priorities.join(', ')}]`);
|
|
166
166
|
|
|
167
167
|
// --- 15. Table Prompt (New) ---
|
|
168
168
|
const userId = await MepCLI.table({
|
|
@@ -175,21 +175,72 @@ async function runComprehensiveDemo() {
|
|
|
175
175
|
{ value: 4, row: ["004", "David", "Manager", "Active"] },
|
|
176
176
|
]
|
|
177
177
|
});
|
|
178
|
-
console.log(`\n
|
|
178
|
+
console.log(`\n Table Result: Selected User ID: ${userId}`);
|
|
179
179
|
|
|
180
180
|
// --- 16. Confirm Prompt (Simple Yes/No) ---
|
|
181
181
|
const proceed = await MepCLI.confirm({
|
|
182
182
|
message: "Ready to deploy the project now?",
|
|
183
183
|
initial: true
|
|
184
184
|
});
|
|
185
|
-
console.log(`\n
|
|
186
|
-
|
|
187
|
-
// --- 17.
|
|
185
|
+
console.log(`\n Confirm Result: Deployment decision: ${proceed ? 'Proceed' : 'Cancel'}`);
|
|
186
|
+
|
|
187
|
+
// --- 17. Editor Prompt (New) ---
|
|
188
|
+
const bio = await MepCLI.editor({
|
|
189
|
+
message: "Write your biography (opens default editor):",
|
|
190
|
+
initial: "Hi, I am a developer...",
|
|
191
|
+
extension: ".md",
|
|
192
|
+
waitUserInput: true
|
|
193
|
+
});
|
|
194
|
+
console.log(`\n Editor Result: Biography length: ${bio.length} chars`);
|
|
195
|
+
|
|
196
|
+
// --- 18. Keypress Prompt (New) ---
|
|
197
|
+
console.log("\n--- Press any key to continue to the Tree Prompt Demo... ---");
|
|
198
|
+
const key = await MepCLI.keypress({
|
|
199
|
+
message: "Press any key to proceed (or 'q' to quit):",
|
|
200
|
+
keys: ['q', 'enter', 'space', 'escape'] // Optional whitelist, or leave undefined for any
|
|
201
|
+
});
|
|
202
|
+
console.log(`\n Keypress Result: You pressed '${key}'`);
|
|
203
|
+
if (key === 'q') return;
|
|
204
|
+
|
|
205
|
+
// --- 19. Tree Prompt (New) ---
|
|
206
|
+
const selectedFile = await MepCLI.tree({
|
|
207
|
+
message: "Select a file from the project structure (Space to toggle, Enter to select):",
|
|
208
|
+
data: [
|
|
209
|
+
{
|
|
210
|
+
title: "src",
|
|
211
|
+
value: "src",
|
|
212
|
+
children: [
|
|
213
|
+
{ title: "index.ts", value: "src/index.ts" },
|
|
214
|
+
{ title: "utils.ts", value: "src/utils.ts" },
|
|
215
|
+
{
|
|
216
|
+
title: "prompts",
|
|
217
|
+
value: "src/prompts",
|
|
218
|
+
expanded: true,
|
|
219
|
+
children: [
|
|
220
|
+
{ title: "text.ts", value: "src/prompts/text.ts" },
|
|
221
|
+
{ title: "select.ts", value: "src/prompts/select.ts" }
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
title: "package.json",
|
|
228
|
+
value: "package.json"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
title: "README.md",
|
|
232
|
+
value: "README.md"
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
console.log(`\n Tree Result: Selected path: ${selectedFile}`);
|
|
237
|
+
|
|
238
|
+
// --- 20. Spin Utility (Loading/Async Task Indicator) ---
|
|
188
239
|
const s = MepCLI.spinner("Finalizing configuration and deploying...").start();
|
|
189
240
|
await new Promise(resolve => setTimeout(resolve, 1500)); // Simulates a 1.5 second async task
|
|
190
241
|
s.success();
|
|
191
242
|
|
|
192
|
-
console.log("\n--- Deployment successful! All MepCLI features demonstrated! ---");
|
|
243
|
+
console.log("\n--- Deployment successful! All MepCLI features (including Editor) demonstrated! ---");
|
|
193
244
|
|
|
194
245
|
} catch (e) {
|
|
195
246
|
// Global handler for Ctrl+C closure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mepcli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Zero-dependency, minimalist interactive CLI prompt for Node.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"prepublishOnly": "npm run build",
|
|
20
|
-
"test": "
|
|
20
|
+
"test": "jest",
|
|
21
|
+
"demo": "ts-node example.ts",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"lint:fix": "eslint . --fix"
|
|
21
24
|
},
|
|
22
25
|
"keywords": [
|
|
23
26
|
"cli",
|
|
@@ -30,8 +33,15 @@
|
|
|
30
33
|
"author": "CodeTease",
|
|
31
34
|
"license": "MIT",
|
|
32
35
|
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^9",
|
|
37
|
+
"@types/jest": "^30",
|
|
33
38
|
"@types/node": "^22",
|
|
39
|
+
"eslint": "^9",
|
|
40
|
+
"globals": "^17",
|
|
41
|
+
"jest": "^30",
|
|
42
|
+
"ts-jest": "^29",
|
|
34
43
|
"ts-node": "^10",
|
|
35
|
-
"typescript": "^5"
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"typescript-eslint": "^8"
|
|
36
46
|
}
|
|
37
47
|
}
|