mepcli 0.4.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.
@@ -0,0 +1,33 @@
1
+ export declare class Spinner {
2
+ private text;
3
+ private timer?;
4
+ private frameIndex;
5
+ private isSpinning;
6
+ constructor(text: string);
7
+ /**
8
+ * Starts the spinner animation.
9
+ */
10
+ start(): this;
11
+ /**
12
+ * Stops the spinner animation.
13
+ */
14
+ stop(): this;
15
+ /**
16
+ * Updates the spinner text.
17
+ */
18
+ update(text: string): this;
19
+ /**
20
+ * Stops the spinner and shows a success message.
21
+ */
22
+ success(message?: string): this;
23
+ /**
24
+ * Stops the spinner and shows an error message.
25
+ */
26
+ error(message?: string): this;
27
+ /**
28
+ * Stops the spinner and clears the line.
29
+ */
30
+ clear(): this;
31
+ private render;
32
+ private handleSignal;
33
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Spinner = void 0;
4
+ const ansi_1 = require("./ansi");
5
+ const symbols_1 = require("./symbols");
6
+ const theme_1 = require("./theme");
7
+ class Spinner {
8
+ constructor(text) {
9
+ this.frameIndex = 0;
10
+ this.isSpinning = false;
11
+ this.handleSignal = () => {
12
+ this.stop();
13
+ process.exit(0);
14
+ };
15
+ this.text = text;
16
+ }
17
+ /**
18
+ * Starts the spinner animation.
19
+ */
20
+ start() {
21
+ if (this.isSpinning)
22
+ return this;
23
+ this.isSpinning = true;
24
+ process.stdout.write(ansi_1.ANSI.HIDE_CURSOR);
25
+ // Render immediately
26
+ this.render();
27
+ // Start loop
28
+ this.timer = setInterval(() => {
29
+ this.render();
30
+ }, 80);
31
+ // Register signal handler to restore cursor on Ctrl+C
32
+ process.on('SIGINT', this.handleSignal);
33
+ return this;
34
+ }
35
+ /**
36
+ * Stops the spinner animation.
37
+ */
38
+ stop() {
39
+ if (this.timer) {
40
+ clearInterval(this.timer);
41
+ this.timer = undefined;
42
+ }
43
+ if (this.isSpinning) {
44
+ this.isSpinning = false;
45
+ process.stdout.write(ansi_1.ANSI.SHOW_CURSOR);
46
+ process.removeListener('SIGINT', this.handleSignal);
47
+ }
48
+ return this;
49
+ }
50
+ /**
51
+ * Updates the spinner text.
52
+ */
53
+ update(text) {
54
+ this.text = text;
55
+ return this;
56
+ }
57
+ /**
58
+ * Stops the spinner and shows a success message.
59
+ */
60
+ success(message) {
61
+ this.stop();
62
+ const text = message ?? this.text;
63
+ process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${theme_1.theme.success}${symbols_1.symbols.tick}${ansi_1.ANSI.RESET} ${text}\n`);
64
+ return this;
65
+ }
66
+ /**
67
+ * Stops the spinner and shows an error message.
68
+ */
69
+ error(message) {
70
+ this.stop();
71
+ const text = message ?? this.text;
72
+ process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${theme_1.theme.error}${symbols_1.symbols.cross}${ansi_1.ANSI.RESET} ${text}\n`);
73
+ return this;
74
+ }
75
+ /**
76
+ * Stops the spinner and clears the line.
77
+ */
78
+ clear() {
79
+ this.stop();
80
+ process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}`);
81
+ return this;
82
+ }
83
+ render() {
84
+ const frame = symbols_1.symbols.spinner[this.frameIndex];
85
+ process.stdout.write(`${ansi_1.ANSI.ERASE_LINE}${ansi_1.ANSI.CURSOR_LEFT}${theme_1.theme.main}${frame}${ansi_1.ANSI.RESET} ${this.text}`);
86
+ this.frameIndex = (this.frameIndex + 1) % symbols_1.symbols.spinner.length;
87
+ }
88
+ }
89
+ exports.Spinner = Spinner;
package/dist/types.d.ts CHANGED
@@ -91,3 +91,42 @@ export interface FileOptions extends BaseOptions {
91
91
  }
92
92
  export interface MultiSelectOptions<V> extends CheckboxOptions<V> {
93
93
  }
94
+ export interface AutocompleteOptions<V> extends BaseOptions {
95
+ suggest: (input: string) => Promise<SelectChoice<V>[]>;
96
+ limit?: number;
97
+ fallback?: string;
98
+ initial?: string;
99
+ }
100
+ export interface SortOptions extends BaseOptions {
101
+ items: string[];
102
+ }
103
+ export interface EditorOptions extends BaseOptions {
104
+ initial?: string;
105
+ extension?: string;
106
+ waitUserInput?: boolean;
107
+ }
108
+ export interface TableRow<V> {
109
+ value: V;
110
+ row: string[];
111
+ }
112
+ export interface TableOptions<V> extends BaseOptions {
113
+ columns: string[];
114
+ data: TableRow<V>[];
115
+ rows?: number;
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 ((str[i] >= '@' && str[i] <= '~') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z')) {
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
@@ -4,11 +4,11 @@ import { MepCLI } from './src'; // Or 'mepcli' if installed via NPM
4
4
  * Runs a comprehensive demo showcasing all MepCLI prompt types and utilities.
5
5
  * This demonstrates all core functionalities including Text, Password, Select,
6
6
  * Checkbox, Number, Toggle, Confirm, List, Slider, Date, File, MultiSelect,
7
- * and the Spin utility.
7
+ * Autocomplete, Sort, Table, and the Spin utility.
8
8
  */
9
9
  async function runComprehensiveDemo() {
10
10
  console.clear();
11
- console.log("--- MepCLI Comprehensive Demo (All 12 Prompts + Spin Utility) ---\n");
11
+ console.log("--- MepCLI Comprehensive Demo (All 15 Prompts + Spin Utility) ---\n");
12
12
 
13
13
  try {
14
14
  // --- 1. Text Prompt (Input with Validation and initial value) ---
@@ -21,14 +21,14 @@ async function runComprehensiveDemo() {
21
21
  return true;
22
22
  }
23
23
  });
24
- console.log(`\n Text Result: Project name set to '${projectName}'`);
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 Password Result: API key entered (length: ${apiKey.length})`);
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 Select Result: Chosen theme is: ${theme}`);
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 Checkbox Result: Selected build tools: [${buildTools.join(', ')}]`);
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 Number Result: Server port: ${port}`);
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 Toggle Result: HTTPS enabled: ${isSecure}`);
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 List Result: Keywords: [${keywords.join(', ')}]`);
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 Slider Result: Brightness: ${brightness}%`);
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 Rating Result: You rated it: ${userRating}/5`);
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 Date Result: Release set for: ${releaseDate.toLocaleString()}`);
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 File Result: Path: ${configPath}`);
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,21 +135,112 @@ async function runComprehensiveDemo() {
135
135
  ],
136
136
  min: 1
137
137
  });
138
- console.log(`\n MultiSelect Result: Linters: [${linters.join(', ')}]`);
138
+ console.log(`\n MultiSelect Result: Linters: [${linters.join(', ')}]`);
139
+
140
+ // --- 13. Autocomplete Prompt (New) ---
141
+ const city = await MepCLI.autocomplete({
142
+ message: "Search for a city (simulated async):",
143
+ suggest: async (query) => {
144
+ const cities = [
145
+ { title: "New York", value: "NY" },
146
+ { title: "London", value: "LDN" },
147
+ { title: "Paris", value: "PAR" },
148
+ { title: "Tokyo", value: "TKY" },
149
+ { title: "Berlin", value: "BER" },
150
+ { title: "San Francisco", value: "SF" },
151
+ { title: "Toronto", value: "TOR" }
152
+ ];
153
+ // Simulate delay
154
+ await new Promise(r => setTimeout(r, 400));
155
+ return cities.filter(c => c.title.toLowerCase().includes(query.toLowerCase()));
156
+ }
157
+ });
158
+ console.log(`\n Autocomplete Result: City code: ${city}`);
159
+
160
+ // --- 14. Sort Prompt (New) ---
161
+ const priorities = await MepCLI.sort({
162
+ message: "Rank your top priorities (Space to grab/drop, Arrows to move):",
163
+ items: ["Performance", "Security", "Features", "Usability", "Cost"]
164
+ });
165
+ console.log(`\n Sort Result: Priorities: [${priorities.join(', ')}]`);
166
+
167
+ // --- 15. Table Prompt (New) ---
168
+ const userId = await MepCLI.table({
169
+ message: "Select a user from the database:",
170
+ columns: ["ID", "Name", "Role", "Status"],
171
+ data: [
172
+ { value: 1, row: ["001", "Alice", "Admin", "Active"] },
173
+ { value: 2, row: ["002", "Bob", "Dev", "Offline"] },
174
+ { value: 3, row: ["003", "Charlie", "User", "Active"] },
175
+ { value: 4, row: ["004", "David", "Manager", "Active"] },
176
+ ]
177
+ });
178
+ console.log(`\n Table Result: Selected User ID: ${userId}`);
139
179
 
140
- // --- 13. Confirm Prompt (Simple Yes/No) ---
180
+ // --- 16. Confirm Prompt (Simple Yes/No) ---
141
181
  const proceed = await MepCLI.confirm({
142
182
  message: "Ready to deploy the project now?",
143
183
  initial: true
144
184
  });
145
- console.log(`\n Confirm Result: Deployment decision: ${proceed ? 'Proceed' : 'Cancel'}`);
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`);
146
195
 
147
- // --- 14. Spin Utility (Loading/Async Task Indicator) ---
148
- await MepCLI.spin(
149
- "Finalizing configuration and deploying...",
150
- new Promise(resolve => setTimeout(resolve, 1500)) // Simulates a 1.5 second async task
151
- );
152
- console.log("\n--- Deployment successful! All MepCLI features demonstrated! ---");
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) ---
239
+ const s = MepCLI.spinner("Finalizing configuration and deploying...").start();
240
+ await new Promise(resolve => setTimeout(resolve, 1500)); // Simulates a 1.5 second async task
241
+ s.success();
242
+
243
+ console.log("\n--- Deployment successful! All MepCLI features (including Editor) demonstrated! ---");
153
244
 
154
245
  } catch (e) {
155
246
  // Global handler for Ctrl+C closure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mepcli",
3
- "version": "0.4.0",
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": "ts-node example.ts"
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
  }