mepcli 1.2.0 → 1.3.1

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 CHANGED
@@ -11,6 +11,10 @@
11
11
 
12
12
  A **CodeTease** project.
13
13
 
14
+ ## Documentation
15
+
16
+ For comprehensive guides, API references, and advanced usage, please visit the **[Documentation](./docs/README.md)**.
17
+
14
18
  ## Features
15
19
 
16
20
  - **Zero Dependency:** Keeps your project clean and fast.
@@ -147,7 +151,7 @@ Complex interfaces for structured data.
147
151
  | `box` | Box model (CSS Margin/Padding) editor. |
148
152
  | `phone` | International phone input with masking & country search. |
149
153
  | `connectionString` | Database URL wizard. |
150
- | `curl` (experimental) | Interactive HTTP request builder. |
154
+ | `curl` | Interactive HTTP request builder. |
151
155
 
152
156
  ### 🔹 Gamified & Fun
153
157
  Add some personality to your CLI.
@@ -166,7 +170,7 @@ Helper functions for better CLI UX.
166
170
 
167
171
  | Function | Description |
168
172
  | :--- | :--- |
169
- | `exec` (experimental) | Run shell command with spinner. |
173
+ | `exec` | Run shell command with spinner. |
170
174
  | `scroll` | Scrollable text viewer (e.g., License). |
171
175
  | `diff` | Text merge conflict resolver. |
172
176
  | `editor` | Open external editor (Vim/Nano). |
@@ -222,363 +226,8 @@ const result = await MepCLI.pipeline()
222
226
 
223
227
  ## Keyboard & Mouse Support
224
228
 
225
- MepCLI automatically detects modern terminals and enables **Mouse Tracking** (using SGR 1006 protocol) for scrolling and clicking.
226
-
227
- ### ⌨️ Advanced Shortcuts
228
-
229
- <details>
230
- <summary><b>Calendar Prompt</b></summary>
231
-
232
- Mep's Calendar prompt supports advanced navigation and selection shortcuts for power users.
233
-
234
- * **Keyboard:**
235
- * `Arrow Keys`: Move cursor day by day.
236
- * `PageUp` / `PageDown`: Jump to previous/next **Month**.
237
- * `Ctrl + Up` / `Ctrl + Down`: Jump to previous/next **Year**.
238
- * `Home` / `End`: Jump to the first/last day of the current month.
239
- * `t`: Jump immediately to **Today**.
240
- * `Enter`: Select date (or start/end of range).
241
-
242
- * **Mouse:**
243
- * `Scroll`: Navigate **Months**.
244
- * `Ctrl + Scroll`: Adjust the selected **Day** (cursor movement).
245
-
246
- </details>
247
-
248
- <details>
249
- <summary><b>Color Prompt</b></summary>
250
-
251
- * **Keyboard:**
252
- * `Tab`: Switch between RGB channels.
253
- * `Up` / `Down`: Move between channels.
254
- * `Left` / `Right`: Adjust current channel value.
255
- * `Shift + Left` / `Shift + Right`: Fast adjust current channel value.
256
-
257
- * **Mouse:**
258
- * `Scroll`: Adjust the current channel value.
259
- * `Ctrl + Scroll`: Fast adjust.
260
-
261
- </details>
262
-
263
- <details>
264
- <summary><b>Checkbox Prompt</b></summary>
265
-
266
- * **Keyboard:**
267
- * `Space`: Toggle selection.
268
- * `a`: Select **All**.
269
- * `x` / `n`: Select **None**.
270
- * `i`: **Invert** selection.
271
-
272
- </details>
273
-
274
- <details>
275
- <summary><b>MultiSelect Prompt</b></summary>
276
-
277
- * **Keyboard:**
278
- * `Space`: Toggle selection.
279
- * `Ctrl + A`: Select **All** (Visible).
280
- * `Ctrl + X`: Deselect **All** (Visible).
281
- * `Typing`: Filter list.
282
-
283
- </details>
284
-
285
- <details>
286
- <summary><b>Transfer Prompt</b></summary>
287
-
288
- * **Keyboard:**
289
- * `Tab` / `Left` / `Right`: Switch focus between Source and Target.
290
- * `Space`: Move selected item.
291
- * `a` / `>`: Move **All** to Target.
292
- * `r` / `<`: Move **All** to Source (Reset).
293
-
294
- </details>
295
-
296
- <details>
297
- <summary><b>Tree & TreeSelect Prompt</b></summary>
298
-
299
- * **Keyboard:**
300
- * `Right`: Expand folder or jump to child.
301
- * `Left`: Collapse folder or jump to parent.
302
- * `Space`: Toggle expansion (Tree) or Checkbox (TreeSelect).
303
- * `e`: **Expand** all recursively.
304
- * `c`: **Collapse** all recursively.
305
-
306
- </details>
307
-
308
- <details>
309
- <summary><b>Grid Prompt</b></summary>
310
-
311
- The Grid prompt (Matrix selection) includes robust shortcuts for bulk actions.
312
-
313
- * **Keyboard:**
314
- * `Arrow Keys`: Move cursor.
315
- * `PageUp` / `PageDown`: Jump to the first/last **Row**.
316
- * `Home` / `End`: Jump to the first/last **Column**.
317
- * `Space`: Toggle current cell.
318
- * `r`: Toggle entire **Row**.
319
- * `c`: Toggle entire **Column**.
320
- * `a`: Select **All**.
321
- * `x`: Deselect **All** (None).
322
- * `i`: **Invert** selection.
323
-
324
- * **Mouse:**
325
- * `Scroll`: Vertical navigation (Rows).
326
- * `Shift + Scroll`: Horizontal navigation (Columns).
327
-
328
- </details>
329
-
330
- <details>
331
- <summary><b>Map Prompt</b></summary>
332
-
333
- * **Keyboard:**
334
- * `Ctrl + N`: Add new row.
335
- * `Ctrl + D`: Delete current row.
336
- * `Arrows` / `Tab`: Navigate cells.
337
-
338
- </details>
339
-
340
- <details>
341
- <summary><b>IP Prompt</b></summary>
342
-
343
- * **Keyboard:**
344
- * `typing...`: Auto-jumps to next octet after 3 digits or `.`.
345
- * `Backspace`: Navigates back to previous octet if empty.
346
-
347
- </details>
348
-
349
- <details>
350
- <summary><b>Kanban Prompt</b></summary>
351
-
352
- * **Keyboard:**
353
- * `Arrows`: Navigate items/columns.
354
- * `Space`: Grab/Drop item (Drag & Drop mode).
355
- * `Enter`: Submit.
356
-
357
- * **Mouse:**
358
- * `Scroll`: Navigate items (Normal) or Move item Left/Right (Grabbed).
359
-
360
- </details>
361
-
362
- <details>
363
- <summary><b>Time Prompt</b></summary>
364
-
365
- * **Keyboard:**
366
- * `Up` / `Down`: Adjust value.
367
- * `Left` / `Right` / `Tab`: Switch unit (Hour/Minute/AM-PM).
368
-
369
- * **Mouse:**
370
- * `Scroll`: Adjust value (Up/Down).
371
-
372
- </details>
373
-
374
- <details>
375
- <summary><b>Heatmap Prompt</b></summary>
376
-
377
- * **Keyboard:**
378
- * `Arrows`: Navigate cells.
379
- * `Tab` / `Shift+Tab`: Navigate cells (Horizontal).
380
- * `Space`: Cycle value.
381
- * `0-9`: Set value directly.
382
-
383
- * **Mouse:**
384
- * `Scroll`: Navigate rows (Vertical).
385
-
386
- </details>
387
-
388
- <details>
389
- <summary><b>Emoji Prompt & MultiColumnSelect</b></summary>
390
-
391
- * **Keyboard:**
392
- * `Arrows`: Navigate grid.
393
- * `Typing`: Filter/Search (Emoji only).
394
-
395
- </details>
396
-
397
- <details>
398
- <summary><b>Miller Prompt</b></summary>
399
-
400
- * **Keyboard:**
401
- * `Up` / `Down`: Navigate items.
402
- * `Right` / `Enter` / `Tab`: Expand child or Drill down.
403
- * `Left` / `Shift + Tab`: Collapse or Go back.
404
-
405
- </details>
406
-
407
- <details>
408
- <summary><b>Match Prompt</b></summary>
409
-
410
- * **Keyboard:**
411
- * `Arrows`: Navigate lists.
412
- * `Tab`: Switch Source/Target.
413
- * `Space`: Pick Source or Toggle Link.
414
-
415
- </details>
416
-
417
- <details>
418
- <summary><b>Diff Prompt</b></summary>
419
-
420
- * **Keyboard:**
421
- * `Left` / `Right`: Switch Action (Original / Modified / Edit).
422
- * `Enter`: Submit selection.
423
-
424
- </details>
425
-
426
- <details>
427
- <summary><b>Dial Prompt</b></summary>
428
-
429
- * **Keyboard:**
430
- * `Arrows`: Adjust value (rotate knob).
431
- * `Enter`: Submit.
432
-
433
- * **Mouse:**
434
- * `Scroll`: Adjust value.
435
-
436
- </details>
437
-
438
- <details>
439
- <summary><b>Draw Prompt</b></summary>
440
-
441
- * **Keyboard:**
442
- * `Arrows`: Move cursor.
443
- * `Space`: Toggle pixel.
444
- * `c`: Clear canvas.
445
- * `i`: Invert canvas.
446
- * `Enter`: Submit.
447
-
448
- * **Mouse:**
449
- * `Drag`: Paint (Left Click) or Erase (Right Click).
450
- * `Click`: Toggle pixel.
451
-
452
- </details>
453
-
454
- <details>
455
- <summary><b>Breadcrumb Prompt</b></summary>
456
-
457
- * **Keyboard:**
458
- * `Arrows`: Navigate list.
459
- * `Enter`: Drill down into folder.
460
- * `Backspace`: Go up one level.
461
-
462
- * **Mouse:**
463
- * `Scroll`: Navigate list.
464
-
465
- </details>
466
-
467
- <details>
468
- <summary><b>Schedule Prompt</b></summary>
469
-
470
- * **Keyboard:**
471
- * `Arrows`: Move task in time.
472
- * `Tab` / `Shift + Tab`: Switch between tasks.
473
- * `Shift + Left/Right`: Resize task duration.
474
- * `PageUp` / `PageDown`: Scroll timeline horizontally.
475
-
476
- * **Mouse:**
477
- * `Scroll`: Scroll timeline horizontally.
478
-
479
- </details>
480
-
481
- <details>
482
- <summary><b>Data Inspector</b></summary>
483
-
484
- * **Keyboard:**
485
- * `Space` / `Arrows`: Expand/Collapse nodes.
486
- * `Enter`: Toggle Boolean or Edit String/Number.
487
-
488
- * **Mouse:**
489
- * `Scroll`: Navigate tree.
490
-
491
- </details>
492
-
493
- <details>
494
- <summary><b>Seat Prompt</b></summary>
495
-
496
- * **Keyboard:**
497
- * `Arrows`: Navigate seat grid.
498
- * `Tab` / `Shift+Tab`: Navigate Left/Right.
499
- * `Space`: Select/Deselect seat.
500
-
501
- * **Mouse:**
502
- * `Scroll`: Navigate Up/Down.
503
-
504
- </details>
505
-
506
- <details>
507
- <summary><b>Select Range Prompt & Multi Range Prompt</b></summary>
508
-
509
- * **Keyboard:**
510
- * `Arrows (Up/Down)`: Navigate items.
511
- * `Space`: Set/Unset anchor point (drag start) or commit range (drag end).
512
- * `Enter`: Submit selected range(s).
513
-
514
- </details>
515
-
516
- <details>
517
- <summary><b>Breadcrumb Search Prompt</b></summary>
518
-
519
- * **Keyboard:**
520
- * `Arrows`: Navigate.
521
- * `Typing`: Enter **Search Mode** (filters current folder).
522
- * `Esc`: Exit Search Mode.
523
- * `Enter`: Drill down (Folder) or Select (File).
524
-
525
- </details>
526
-
527
- <details>
528
- <summary><b>Sort Grid Prompt</b></summary>
529
-
530
- * **Keyboard:**
531
- * `Arrows`: Navigate grid.
532
- * `Tab` / `Shift+Tab`: Navigate Left/Right.
533
- * `Space`: Grab/Drop item.
534
- * `Enter`: Submit grid.
535
-
536
- * **Mouse:**
537
- * `Scroll`: Navigate Up/Down.
538
-
539
- </details>
540
-
541
- <details>
542
- <summary><b>Dependency Prompt</b></summary>
543
-
544
- * **Keyboard:**
545
- * `Arrows`: Navigate items.
546
- * `Space`: Toggle item (Triggers auto-resolution).
547
- * `Enter`: Submit selection.
548
-
549
- * **Mouse:**
550
- * `Scroll`: Navigate Up/Down.
551
-
552
- </details>
553
-
554
- <details>
555
- <summary><b>Box Prompt</b></summary>
556
-
557
- * **Keyboard:**
558
- * `Arrows` / `Tab`: Navigate (Top -> Right -> Bottom -> Left).
559
- * `Shift + Tab`: Navigate backwards.
560
- * `+` / `-`: Increment/Decrement value.
561
- * `0-9`: Type value directly.
562
-
563
- * **Mouse:**
564
- * `Scroll`: Cycle focus (Up=Backwards, Down=Forwards).
565
-
566
- </details>
567
-
568
- <details>
569
- <summary><b>Phone Prompt</b></summary>
570
-
571
- * **Keyboard:**
572
- * `Tab`: Switch between **Country Code** and **Number** sections.
573
- * `Typing` (in Country section): Fuzzy search for country (e.g., "Viet", "US").
574
- * `Arrows (Up/Down)`: Cycle through countries.
575
- * `Backspace`: Delete digit or clear search.
576
-
577
- * **Mouse:**
578
- * `Scroll`: Cycle through countries (when Country section is active).
579
-
580
- </details>
229
+ For a detailed list of shortcuts and mouse interactions, please refer to the **[Shortcuts Documentation](./docs/shortcuts.md)**.
581
230
 
582
231
  ## License
583
232
 
584
- This project is under the **MIT License**.
233
+ This project is under the **MIT License**.
package/dist/core.d.ts CHANGED
@@ -7,7 +7,7 @@ import { Pipeline } from './pipeline';
7
7
  import { TaskRunner } from './tasks';
8
8
  import { TaskGroupOptions } from './types';
9
9
  /**
10
- * Public Facade for MepCLI
10
+ * Public Facade for Mep
11
11
  */
12
12
  export declare class MepCLI {
13
13
  static theme: ThemeConfig;
@@ -875,7 +875,7 @@ export declare class MepCLI {
875
875
  * });
876
876
  * @param options - Command string and streaming preferences.
877
877
  * @returns A promise resolving when execution completes.
878
- * @see {@link https://github.com/CodeTease/mep/blob/main/example.ts}
878
+ * @see {@link https://github.com/CodeTease/mep/blob/main/examples/exec-prompt.ts}
879
879
  */
880
880
  static exec(options: ExecOptions): Promise<void>;
881
881
  /**
@@ -1007,7 +1007,6 @@ export declare class MepCLI {
1007
1007
  static connectionString(options: ConnectionStringOptions): Promise<ConnectionStringResult>;
1008
1008
  /**
1009
1009
  * cURL Command Builder.
1010
- * @experimental
1011
1010
  * @example
1012
1011
  * const request = await MepCLI.curl({
1013
1012
  * message: 'Build API Request',
package/dist/core.js CHANGED
@@ -79,7 +79,7 @@ const curl_1 = require("./prompts/curl");
79
79
  const pipeline_1 = require("./pipeline");
80
80
  const tasks_1 = require("./tasks");
81
81
  /**
82
- * Public Facade for MepCLI
82
+ * Public Facade for Mep
83
83
  */
84
84
  class MepCLI {
85
85
  /**
@@ -1072,7 +1072,7 @@ class MepCLI {
1072
1072
  * });
1073
1073
  * @param options - Command string and streaming preferences.
1074
1074
  * @returns A promise resolving when execution completes.
1075
- * @see {@link https://github.com/CodeTease/mep/blob/main/example.ts}
1075
+ * @see {@link https://github.com/CodeTease/mep/blob/main/examples/exec-prompt.ts}
1076
1076
  */
1077
1077
  static exec(options) {
1078
1078
  return new exec_1.ExecPrompt(options).run();
@@ -1219,7 +1219,6 @@ class MepCLI {
1219
1219
  }
1220
1220
  /**
1221
1221
  * cURL Command Builder.
1222
- * @experimental
1223
1222
  * @example
1224
1223
  * const request = await MepCLI.curl({
1225
1224
  * message: 'Build API Request',
@@ -0,0 +1,25 @@
1
+ export type ShellType = 'bash' | 'powershell' | 'cmd';
2
+ export interface ShellStrategy {
3
+ binary: string;
4
+ wrapper: string;
5
+ continuation: string;
6
+ escape(value: string): string;
7
+ }
8
+ export declare class BashStrategy implements ShellStrategy {
9
+ readonly binary = "curl";
10
+ readonly wrapper = "'";
11
+ readonly continuation = " \\";
12
+ escape(value: string): string;
13
+ }
14
+ export declare class PowerShellStrategy implements ShellStrategy {
15
+ readonly binary = "curl.exe";
16
+ readonly wrapper = "'";
17
+ readonly continuation = " `";
18
+ escape(value: string): string;
19
+ }
20
+ export declare class CmdStrategy implements ShellStrategy {
21
+ readonly binary = "curl";
22
+ readonly wrapper = "\"";
23
+ readonly continuation = " ^";
24
+ escape(value: string): string;
25
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CmdStrategy = exports.PowerShellStrategy = exports.BashStrategy = void 0;
4
+ class BashStrategy {
5
+ constructor() {
6
+ this.binary = 'curl';
7
+ this.wrapper = "'";
8
+ this.continuation = ' \\';
9
+ }
10
+ escape(value) {
11
+ // e.g. "It's me" -> 'It'\''s me'
12
+ return `'${value.replace(/'/g, "'\\''")}'`;
13
+ }
14
+ }
15
+ exports.BashStrategy = BashStrategy;
16
+ class PowerShellStrategy {
17
+ constructor() {
18
+ this.binary = 'curl.exe';
19
+ this.wrapper = "'";
20
+ this.continuation = ' `';
21
+ }
22
+ escape(value) {
23
+ // e.g. "It's me" -> 'It''s me'
24
+ return `'${value.replace(/'/g, "''")}'`;
25
+ }
26
+ }
27
+ exports.PowerShellStrategy = PowerShellStrategy;
28
+ class CmdStrategy {
29
+ constructor() {
30
+ this.binary = 'curl';
31
+ this.wrapper = '"';
32
+ this.continuation = ' ^';
33
+ }
34
+ escape(value) {
35
+ // e.g. {"key": "value"} -> "{\"key\": \"value\"}"
36
+ // e.g. value with " -> "value with \""
37
+ const flattened = value.replace(/[\r\n]+/g, ' ');
38
+ return `"${flattened.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
39
+ }
40
+ }
41
+ exports.CmdStrategy = CmdStrategy;
@@ -16,24 +16,25 @@ export interface CurlResult {
16
16
  export declare class CurlPrompt extends Prompt<CurlResult, CurlOptions> {
17
17
  private section;
18
18
  private methodIndex;
19
- private url;
19
+ private urlSegments;
20
+ private urlCursor;
20
21
  private headers;
21
22
  private body;
22
- private urlCursor;
23
23
  private lastLinesUp;
24
+ private shell;
25
+ private strategies;
24
26
  constructor(options: CurlOptions);
25
27
  private get currentMethod();
26
28
  private get hasBody();
27
- /**
28
- * Escape a string for safe inclusion inside a double-quoted shell argument.
29
- * This escapes backslashes first, then double quotes.
30
- */
31
- private shellEscapeDoubleQuoted;
29
+ private get url();
30
+ private cycleShell;
32
31
  private generateCommand;
33
32
  protected render(firstRender: boolean): void;
34
33
  protected cleanup(): void;
35
34
  protected handleInput(char: string, _buffer: Buffer): void;
35
+ private handleUrlInput;
36
36
  private cycleSection;
37
+ private clear;
37
38
  private editHeaders;
38
39
  private editBody;
39
40
  private submitResult;
@@ -7,6 +7,8 @@ const theme_1 = require("../theme");
7
7
  const symbols_1 = require("../symbols");
8
8
  const map_1 = require("./map");
9
9
  const code_1 = require("./code");
10
+ const utils_1 = require("../utils");
11
+ const curl_utils_1 = require("./curl-utils");
10
12
  var Section;
11
13
  (function (Section) {
12
14
  Section[Section["METHOD"] = 0] = "METHOD";
@@ -15,26 +17,45 @@ var Section;
15
17
  Section[Section["BODY"] = 3] = "BODY";
16
18
  })(Section || (Section = {}));
17
19
  const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
20
+ const COMMON_HEADERS = [
21
+ 'Accept', 'Accept-Encoding', 'Accept-Language',
22
+ 'Authorization', 'Cache-Control', 'Connection',
23
+ 'Content-Type', 'Cookie', 'Host',
24
+ 'Origin', 'Pragma', 'Referer',
25
+ 'User-Agent', 'X-Requested-With'
26
+ ];
18
27
  class CurlPrompt extends base_1.Prompt {
19
28
  constructor(options) {
20
29
  super(options);
21
30
  this.section = Section.METHOD;
22
31
  this.methodIndex = 0;
23
- this.url = '';
32
+ // URL State
33
+ this.urlSegments = [];
34
+ this.urlCursor = 0;
24
35
  this.headers = {};
25
36
  this.body = '';
26
- // For URL input
27
- this.urlCursor = 0;
37
+ // Render State
28
38
  this.lastLinesUp = 0;
29
- this.warnExperimental();
39
+ // Shell State
40
+ this.shell = 'bash';
41
+ this.strategies = {
42
+ bash: new curl_utils_1.BashStrategy(),
43
+ powershell: new curl_utils_1.PowerShellStrategy(),
44
+ cmd: new curl_utils_1.CmdStrategy()
45
+ };
46
+ // Auto-detect shell
47
+ if (process.platform === 'win32') {
48
+ this.shell = 'powershell';
49
+ }
30
50
  // Initialize state
31
51
  if (options.defaultMethod) {
32
52
  const idx = METHODS.indexOf(options.defaultMethod.toUpperCase());
33
53
  if (idx >= 0)
34
54
  this.methodIndex = idx;
35
55
  }
36
- this.url = options.defaultUrl || '';
37
- this.urlCursor = this.url.length;
56
+ const initialUrl = options.defaultUrl || '';
57
+ this.urlSegments = (0, utils_1.safeSplit)(initialUrl);
58
+ this.urlCursor = this.urlSegments.length;
38
59
  this.headers = { ...options.defaultHeaders };
39
60
  this.body = options.defaultBody || '';
40
61
  // Auto-select URL if method is GET (default)
@@ -48,32 +69,36 @@ class CurlPrompt extends base_1.Prompt {
48
69
  get hasBody() {
49
70
  return this.currentMethod !== 'GET' && this.currentMethod !== 'HEAD';
50
71
  }
51
- /**
52
- * Escape a string for safe inclusion inside a double-quoted shell argument.
53
- * This escapes backslashes first, then double quotes.
54
- */
55
- shellEscapeDoubleQuoted(value) {
56
- return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
72
+ get url() {
73
+ return this.urlSegments.join('');
74
+ }
75
+ cycleShell() {
76
+ const shells = ['bash', 'powershell', 'cmd'];
77
+ const currentIdx = shells.indexOf(this.shell);
78
+ this.shell = shells[(currentIdx + 1) % shells.length];
79
+ this.render(false);
57
80
  }
58
- generateCommand() {
59
- let cmd = `curl -X ${this.currentMethod}`;
81
+ generateCommand(multiline = false) {
82
+ // Force single line for CMD
83
+ if (this.shell === 'cmd') {
84
+ multiline = false;
85
+ }
86
+ const strategy = this.strategies[this.shell];
87
+ const continuation = multiline ? `${strategy.continuation}\n ` : ' ';
88
+ let cmd = `${strategy.binary} -X ${this.currentMethod}`;
60
89
  // Headers
61
90
  Object.entries(this.headers).forEach(([k, v]) => {
62
- cmd += ` -H "${k}: ${v}"`;
91
+ cmd += `${continuation}-H ${strategy.escape(`${k}: ${v}`)}`;
63
92
  });
64
93
  // Body
65
94
  if (this.hasBody && this.body) {
66
- // Escape body for shell
67
- const escapedBody = this.shellEscapeDoubleQuoted(this.body);
68
- cmd += ` -d "${escapedBody}"`;
95
+ const escapedBody = strategy.escape(this.body);
96
+ cmd += `${continuation}-d ${escapedBody}`;
69
97
  }
70
98
  // URL
71
- if (this.url) {
72
- cmd += ` "${this.url}"`;
73
- }
74
- else {
75
- cmd += ` "http://localhost..."`;
76
- }
99
+ const urlStr = this.url;
100
+ const displayUrl = urlStr || 'http://localhost...';
101
+ cmd += `${continuation}${strategy.escape(displayUrl)}`;
77
102
  return cmd;
78
103
  }
79
104
  render(firstRender) {
@@ -83,7 +108,8 @@ class CurlPrompt extends base_1.Prompt {
83
108
  this.lastLinesUp = 0;
84
109
  let output = '';
85
110
  // Title
86
- output += `${theme_1.theme.success}? ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
111
+ const shellLabel = `${theme_1.theme.muted}[Shell: ${this.shell.toUpperCase()}]${ansi_1.ANSI.RESET}`;
112
+ output += `${theme_1.theme.success}? ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message} ${shellLabel}${ansi_1.ANSI.RESET}\n`;
87
113
  // 1. Method
88
114
  const methodLabel = this.section === Section.METHOD
89
115
  ? `${theme_1.theme.main}${ansi_1.ANSI.REVERSE} ${this.currentMethod} ${ansi_1.ANSI.RESET}`
@@ -92,16 +118,16 @@ class CurlPrompt extends base_1.Prompt {
92
118
  // 2. URL
93
119
  const urlActive = this.section === Section.URL;
94
120
  const urlPrefix = urlActive ? `${theme_1.theme.main}${ansi_1.ANSI.BOLD} URL: ${ansi_1.ANSI.RESET}` : ` URL: `;
95
- let urlDisplay = this.url;
96
- if (!urlDisplay && urlActive) {
121
+ let urlDisplay = '';
122
+ if (this.urlSegments.length === 0 && urlActive) {
123
+ // Placeholder/Empty state when active
124
+ urlDisplay = '';
125
+ }
126
+ else if (this.urlSegments.length === 0 && !urlActive) {
97
127
  urlDisplay = `${theme_1.theme.muted}http://localhost:3000${ansi_1.ANSI.RESET}`;
98
128
  }
99
- // Insert cursor for URL
100
- if (urlActive) {
101
- const beforeCursor = urlDisplay.slice(0, this.urlCursor);
102
- const atCursor = urlDisplay.slice(this.urlCursor, this.urlCursor + 1) || ' ';
103
- const afterCursor = urlDisplay.slice(this.urlCursor + 1);
104
- urlDisplay = `${beforeCursor}${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${atCursor}${ansi_1.ANSI.RESET}${afterCursor}`;
129
+ else {
130
+ urlDisplay = this.urlSegments.join('');
105
131
  }
106
132
  output += `${urlPrefix}${urlDisplay}\n`;
107
133
  // 3. Headers
@@ -130,26 +156,39 @@ class CurlPrompt extends base_1.Prompt {
130
156
  }
131
157
  // Preview
132
158
  output += `\n${ansi_1.ANSI.BOLD}Preview:${ansi_1.ANSI.RESET}\n`;
133
- const cmd = this.generateCommand();
159
+ // Use multiline mode for preview
160
+ const cmd = this.generateCommand(true);
134
161
  // Syntax highlight command (basic)
135
162
  output += `${ansi_1.ANSI.FG_CYAN}${cmd}${ansi_1.ANSI.RESET}\n`;
136
163
  // Instructions
137
- output += `\n${theme_1.theme.muted}(Tab: Nav, Space: Toggle Method, Enter: Edit/Submit)${ansi_1.ANSI.RESET}`;
164
+ output += `\n${theme_1.theme.muted}(Tab: Nav, 's': Shell, Space: Toggle Method, Enter: Edit/Submit)${ansi_1.ANSI.RESET}`;
138
165
  this.renderFrame(output);
139
166
  // Cursor Positioning
140
167
  if (this.section === Section.URL) {
141
168
  const prefixLen = 6; // " URL: "
142
- // We need to move cursor to (row 3, prefixLen + this.urlCursor)
143
- const lines = output.split('\n');
144
- const urlLineIndex = lines.findIndex(l => l.includes(' URL: '));
145
- const linesFromBottom = lines.length - 1 - urlLineIndex;
146
- this.print(ansi_1.ANSI.SHOW_CURSOR);
147
- if (linesFromBottom > 0) {
148
- this.print(`\x1b[${linesFromBottom}A`);
149
- this.lastLinesUp = linesFromBottom;
169
+ // Use lastRenderLines to find the exact visual line index
170
+ let urlLineIndex = -1;
171
+ for (let i = 0; i < this.lastRenderLines.length; i++) {
172
+ if (this.lastRenderLines[i].includes(' URL: ')) {
173
+ urlLineIndex = i;
174
+ break;
175
+ }
176
+ }
177
+ if (urlLineIndex !== -1) {
178
+ const linesFromBottom = this.lastRenderLines.length - 1 - urlLineIndex;
179
+ this.print(ansi_1.ANSI.SHOW_CURSOR);
180
+ if (linesFromBottom > 0) {
181
+ this.print(`\x1b[${linesFromBottom}A`);
182
+ this.lastLinesUp = linesFromBottom;
183
+ }
184
+ // Calculate visual cursor position
185
+ let targetCol = prefixLen;
186
+ // Add width of segments up to cursor
187
+ for (let i = 0; i < this.urlCursor; i++) {
188
+ targetCol += (0, utils_1.stringWidth)(this.urlSegments[i]);
189
+ }
190
+ this.print(`\r\x1b[${targetCol}C`);
150
191
  }
151
- const targetCol = prefixLen + this.urlCursor;
152
- this.print(`\r\x1b[${targetCol}C`);
153
192
  }
154
193
  else {
155
194
  this.print(ansi_1.ANSI.HIDE_CURSOR);
@@ -163,6 +202,11 @@ class CurlPrompt extends base_1.Prompt {
163
202
  super.cleanup();
164
203
  }
165
204
  handleInput(char, _buffer) {
205
+ // Toggle Shell (only when not editing URL)
206
+ if (char === 's' && this.section !== Section.URL) {
207
+ this.cycleShell();
208
+ return;
209
+ }
166
210
  // Navigation
167
211
  if (char === '\t') {
168
212
  this.cycleSection(1);
@@ -186,40 +230,11 @@ class CurlPrompt extends base_1.Prompt {
186
230
  this.render(false);
187
231
  }
188
232
  else if (char === '\r' || char === '\n') {
189
- // Enter on Method -> Submit
190
233
  this.submitResult();
191
234
  }
192
235
  break;
193
236
  case Section.URL:
194
- if (char === '\r' || char === '\n') {
195
- this.submitResult();
196
- return;
197
- }
198
- // Typing
199
- if (char === '\u0008' || char === '\x7f') { // Backspace
200
- if (this.urlCursor > 0) {
201
- this.url = this.url.slice(0, this.urlCursor - 1) + this.url.slice(this.urlCursor);
202
- this.urlCursor--;
203
- this.render(false);
204
- }
205
- }
206
- else if (this.isLeft(char)) {
207
- if (this.urlCursor > 0) {
208
- this.urlCursor--;
209
- this.render(false);
210
- }
211
- }
212
- else if (this.isRight(char)) {
213
- if (this.urlCursor < this.url.length) {
214
- this.urlCursor++;
215
- this.render(false);
216
- }
217
- }
218
- else if (!/^[\x00-\x1F]/.test(char)) {
219
- this.url = this.url.slice(0, this.urlCursor) + char + this.url.slice(this.urlCursor);
220
- this.urlCursor += char.length;
221
- this.render(false);
222
- }
237
+ this.handleUrlInput(char);
223
238
  break;
224
239
  case Section.HEADERS:
225
240
  if (char === '\r' || char === '\n') {
@@ -233,36 +248,139 @@ class CurlPrompt extends base_1.Prompt {
233
248
  break;
234
249
  }
235
250
  }
251
+ handleUrlInput(char) {
252
+ // Submit
253
+ if (char === '\r' || char === '\n') {
254
+ this.submitResult();
255
+ return;
256
+ }
257
+ // Home
258
+ if (char === '\x1b[H' || char === '\x1bOH' || char === '\x1b[1~') {
259
+ this.urlCursor = 0;
260
+ this.render(false);
261
+ return;
262
+ }
263
+ // End
264
+ if (char === '\x1b[F' || char === '\x1bOF' || char === '\x1b[4~') {
265
+ this.urlCursor = this.urlSegments.length;
266
+ this.render(false);
267
+ return;
268
+ }
269
+ // Ctrl+U (Delete to start)
270
+ if (char === '\x15') {
271
+ if (this.urlCursor > 0) {
272
+ this.urlSegments.splice(0, this.urlCursor);
273
+ this.urlCursor = 0;
274
+ this.render(false);
275
+ }
276
+ return;
277
+ }
278
+ // Ctrl+W (Delete word backwards)
279
+ if (char === '\x17') {
280
+ if (this.urlCursor > 0) {
281
+ // Find previous word boundary
282
+ let i = this.urlCursor - 1;
283
+ // Skip trailing spaces
284
+ while (i >= 0 && this.urlSegments[i] === ' ')
285
+ i--;
286
+ // Skip word characters
287
+ while (i >= 0 && this.urlSegments[i] !== ' ')
288
+ i--;
289
+ const deleteCount = this.urlCursor - (i + 1);
290
+ this.urlSegments.splice(i + 1, deleteCount);
291
+ this.urlCursor = i + 1;
292
+ this.render(false);
293
+ }
294
+ return;
295
+ }
296
+ // Backspace
297
+ if (char === '\u0008' || char === '\x7f') {
298
+ if (this.urlCursor > 0) {
299
+ this.urlSegments.splice(this.urlCursor - 1, 1);
300
+ this.urlCursor--;
301
+ this.render(false);
302
+ }
303
+ return;
304
+ }
305
+ // Delete
306
+ if (char === '\u001b[3~') {
307
+ if (this.urlCursor < this.urlSegments.length) {
308
+ this.urlSegments.splice(this.urlCursor, 1);
309
+ this.render(false);
310
+ }
311
+ return;
312
+ }
313
+ // Left
314
+ if (this.isLeft(char)) {
315
+ if (this.urlCursor > 0) {
316
+ this.urlCursor--;
317
+ this.render(false);
318
+ }
319
+ return;
320
+ }
321
+ // Right
322
+ if (this.isRight(char)) {
323
+ if (this.urlCursor < this.urlSegments.length) {
324
+ this.urlCursor++;
325
+ this.render(false);
326
+ }
327
+ return;
328
+ }
329
+ // Regular Typing
330
+ if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
331
+ const newSegments = (0, utils_1.safeSplit)(char);
332
+ this.urlSegments.splice(this.urlCursor, 0, ...newSegments);
333
+ this.urlCursor += newSegments.length;
334
+ this.render(false);
335
+ }
336
+ }
236
337
  cycleSection(direction) {
237
- // Logic to skip disabled Body
238
338
  let next = this.section + direction;
239
- // Loop
240
339
  if (next > Section.BODY)
241
340
  next = Section.METHOD;
242
341
  if (next < Section.METHOD)
243
342
  next = Section.BODY;
244
- // If Body is disabled and we landed on it
245
343
  if (next === Section.BODY && !this.hasBody) {
246
344
  next = direction === 1 ? Section.METHOD : Section.HEADERS;
247
345
  }
248
346
  this.section = next;
249
347
  }
348
+ clear() {
349
+ // 1. Restore cursor to bottom if it was moved up (for URL editing)
350
+ if (this.lastLinesUp > 0) {
351
+ this.print(`\x1b[${this.lastLinesUp}B`);
352
+ this.lastLinesUp = 0;
353
+ }
354
+ // 2. Erase the prompt content
355
+ // We move up (height - 1) lines to the top line, then erase everything below
356
+ if (this.lastRenderHeight > 0) {
357
+ this.print(`\x1b[${this.lastRenderHeight - 1}A`); // Go to top line
358
+ this.print('\r'); // Go to start of line
359
+ this.print(ansi_1.ANSI.ERASE_DOWN); // Erase everything below
360
+ // Reset state so next render is treated as fresh
361
+ this.lastRenderLines = [];
362
+ this.lastRenderHeight = 0;
363
+ }
364
+ }
250
365
  async editHeaders() {
366
+ this.clear(); // Clear UI to prevent artifacts
251
367
  this.pauseInput();
252
368
  try {
253
369
  const result = await new map_1.MapPrompt({
254
370
  message: 'Edit Headers',
255
371
  initial: this.headers,
372
+ suggestions: COMMON_HEADERS
256
373
  }).run();
257
374
  this.headers = result;
258
375
  }
259
376
  catch (_e) {
260
- // Cancelled or error
377
+ // Cancelled
261
378
  }
262
379
  this.resumeInput();
263
- this.render(false); // Re-render our UI
380
+ this.render(true); // Force full re-render
264
381
  }
265
382
  async editBody() {
383
+ this.clear(); // Clear UI to prevent artifacts
266
384
  this.pauseInput();
267
385
  try {
268
386
  const result = await new code_1.CodePrompt({
@@ -277,7 +395,7 @@ class CurlPrompt extends base_1.Prompt {
277
395
  // Cancelled
278
396
  }
279
397
  this.resumeInput();
280
- this.render(false);
398
+ this.render(true); // Force full re-render
281
399
  }
282
400
  submitResult() {
283
401
  this.submit({
@@ -285,7 +403,7 @@ class CurlPrompt extends base_1.Prompt {
285
403
  url: this.url,
286
404
  headers: this.headers,
287
405
  body: this.hasBody ? this.body : undefined,
288
- command: this.generateCommand()
406
+ command: this.generateCommand(false) // Single line for clipboard/usage
289
407
  });
290
408
  }
291
409
  }
@@ -4,8 +4,12 @@ export declare class ExecPrompt extends Prompt<void, ExecOptions> {
4
4
  private child?;
5
5
  private status;
6
6
  private timer?;
7
+ private stdoutBuffer;
8
+ private stderrBuffer;
9
+ private lastLogLine;
7
10
  constructor(options: ExecOptions);
8
11
  run(): Promise<void>;
12
+ private updateLastLogLine;
9
13
  private killChild;
10
14
  protected cleanup(): void;
11
15
  protected render(_firstRender: boolean): void;
@@ -10,20 +10,42 @@ class ExecPrompt extends base_1.Prompt {
10
10
  constructor(options) {
11
11
  super(options);
12
12
  this.status = 'running';
13
- this.warnExperimental();
13
+ this.stdoutBuffer = '';
14
+ this.stderrBuffer = '';
15
+ this.lastLogLine = '';
16
+ // Experimental warning removed
14
17
  }
15
18
  run() {
16
19
  this.child = (0, child_process_1.spawn)(this.options.command, [], {
17
20
  cwd: this.options.cwd || process.cwd(),
18
21
  shell: true,
19
- stdio: this.options.streamOutput ? 'inherit' : 'ignore'
22
+ // Use 'ignore' for stdin so parent keeps control (and raw mode).
23
+ // Use 'pipe' for stdout/stderr to capture output.
24
+ stdio: ['ignore', 'pipe', 'pipe']
20
25
  });
26
+ // Capture stdout
27
+ if (this.child.stdout) {
28
+ this.child.stdout.on('data', (data) => {
29
+ const chunk = data.toString();
30
+ this.stdoutBuffer += chunk;
31
+ this.updateLastLogLine(chunk);
32
+ });
33
+ }
34
+ // Capture stderr
35
+ if (this.child.stderr) {
36
+ this.child.stderr.on('data', (data) => {
37
+ const chunk = data.toString();
38
+ this.stderrBuffer += chunk;
39
+ this.updateLastLogLine(chunk);
40
+ });
41
+ }
21
42
  if (this.options.timeout && this.options.timeout > 0) {
22
43
  this.timer = setTimeout(() => {
23
44
  if (this.status !== 'running')
24
45
  return;
25
46
  this.status = 'error';
26
47
  this.render(false);
48
+ this.killChild();
27
49
  this.cancel(new Error(`Timeout after ${this.options.timeout}ms`));
28
50
  }, this.options.timeout);
29
51
  }
@@ -38,7 +60,15 @@ class ExecPrompt extends base_1.Prompt {
38
60
  else {
39
61
  this.status = 'error';
40
62
  this.render(false);
41
- this.cancel(new Error(`Command failed with exit code ${code}`));
63
+ const errorMessage = this.stderrBuffer.trim() || `Command failed with exit code ${code}`;
64
+ const err = new Error(errorMessage);
65
+ // Attach details
66
+ Object.assign(err, {
67
+ code,
68
+ stdout: this.stdoutBuffer,
69
+ stderr: this.stderrBuffer
70
+ });
71
+ this.cancel(err);
42
72
  }
43
73
  });
44
74
  this.child.on('error', (err) => {
@@ -50,6 +80,19 @@ class ExecPrompt extends base_1.Prompt {
50
80
  });
51
81
  return super.run();
52
82
  }
83
+ updateLastLogLine(chunk) {
84
+ // We only want the last non-empty line
85
+ const lines = chunk.split('\n');
86
+ // Iterate backwards
87
+ for (let i = lines.length - 1; i >= 0; i--) {
88
+ const line = lines[i].trim();
89
+ if (line) {
90
+ this.lastLogLine = line;
91
+ this.render(false);
92
+ break;
93
+ }
94
+ }
95
+ }
53
96
  killChild() {
54
97
  if (this.child && !this.child.killed) {
55
98
  this.child.kill();
@@ -73,11 +116,22 @@ class ExecPrompt extends base_1.Prompt {
73
116
  else if (this.status === 'error') {
74
117
  symbol = theme_1.theme.error + symbols_1.symbols.cross + ansi_1.ANSI.RESET;
75
118
  }
76
- const output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${symbol}`;
119
+ let details = '';
120
+ if (this.status === 'running' && this.lastLogLine) {
121
+ // Truncate for display
122
+ const maxLen = 50;
123
+ let line = this.stripAnsi(this.lastLogLine);
124
+ if (line.length > maxLen) {
125
+ line = line.substring(0, maxLen - 3) + '...';
126
+ }
127
+ details = ` ${theme_1.theme.muted}${line}${ansi_1.ANSI.RESET}`;
128
+ }
129
+ const output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${symbol}${details}`;
77
130
  this.renderFrame(output);
78
131
  }
79
132
  handleInput(_char, _key) {
80
- // Ignore input
133
+ // Prompt base class handles Ctrl+C (SIGINT) in _onKeyHandler
134
+ // which calls cleanup() -> killChild().
81
135
  }
82
136
  }
83
137
  exports.ExecPrompt = ExecPrompt;
@@ -7,9 +7,11 @@ export declare class MapPrompt extends Prompt<Record<string, string>, MapOptions
7
7
  private scrollTop;
8
8
  private readonly pageSize;
9
9
  private errorMsg;
10
+ private ghost;
10
11
  constructor(options: MapOptions);
11
12
  protected render(_firstRender: boolean): void;
12
13
  private pad;
14
+ private updateGhost;
13
15
  protected handleInput(char: string): void;
14
16
  protected handleMouse(event: MouseEvent): void;
15
17
  }
@@ -15,6 +15,7 @@ class MapPrompt extends base_1.Prompt {
15
15
  this.scrollTop = 0;
16
16
  this.pageSize = 7;
17
17
  this.errorMsg = '';
18
+ this.ghost = '';
18
19
  if (options.initial) {
19
20
  this.items = Object.entries(options.initial).map(([key, value]) => ({ key, value }));
20
21
  }
@@ -29,7 +30,11 @@ class MapPrompt extends base_1.Prompt {
29
30
  output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
30
31
  // Layout Calculation
31
32
  const maxKeyWidth = Math.max(5, // Minimum width
32
- ...this.items.map(item => (0, utils_1.stringWidth)(item.key))) + 2; // Padding
33
+ ...this.items.map((item, idx) => {
34
+ // Include ghost length in calculation if it's the active row
35
+ const ghostLen = (idx === this.rowIndex && this.colIndex === 0) ? (0, utils_1.stringWidth)(this.ghost) : 0;
36
+ return (0, utils_1.stringWidth)(item.key) + ghostLen;
37
+ })) + 2; // Padding
33
38
  // Scrolling Logic
34
39
  if (this.rowIndex < this.scrollTop) {
35
40
  this.scrollTop = this.rowIndex;
@@ -53,11 +58,16 @@ class MapPrompt extends base_1.Prompt {
53
58
  const pointer = isRowActive ? `${theme_1.theme.main}${symbols_1.symbols.pointer}${ansi_1.ANSI.RESET} ` : ' ';
54
59
  // Render Key
55
60
  let keyStr = item.key;
61
+ let ghostStr = '';
56
62
  if (isRowActive && this.colIndex === 0) {
57
- keyStr = `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${keyStr}${ansi_1.ANSI.RESET}`;
63
+ if (this.ghost) {
64
+ ghostStr = `${theme_1.theme.muted}${this.ghost}${ansi_1.ANSI.RESET}`;
65
+ }
66
+ keyStr = `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${keyStr}${ansi_1.ANSI.RESET}${ghostStr}`;
58
67
  }
59
- // Adjust padding manually because ANSI codes mess up simple padEnd
60
- const keyVisualWidth = (0, utils_1.stringWidth)(item.key);
68
+ // Adjust padding manually
69
+ // We need visual width of key + ghost (if active)
70
+ const keyVisualWidth = (0, utils_1.stringWidth)(item.key) + (isRowActive && this.colIndex === 0 ? (0, utils_1.stringWidth)(this.ghost) : 0);
61
71
  const padding = ' '.repeat(Math.max(0, maxKeyWidth - keyVisualWidth));
62
72
  // Render Value
63
73
  let valStr = item.value;
@@ -79,12 +89,41 @@ class MapPrompt extends base_1.Prompt {
79
89
  return str;
80
90
  return str + ' '.repeat(width - len);
81
91
  }
92
+ updateGhost() {
93
+ this.ghost = '';
94
+ if (this.colIndex !== 0 || !this.options.suggestions)
95
+ return;
96
+ const currentKey = this.items[this.rowIndex].key;
97
+ if (!currentKey)
98
+ return;
99
+ const lowerKey = currentKey.toLowerCase();
100
+ // Find first match
101
+ const match = this.options.suggestions.find(s => s.toLowerCase().startsWith(lowerKey) && s.length > currentKey.length);
102
+ if (match) {
103
+ this.ghost = match.slice(currentKey.length);
104
+ }
105
+ }
82
106
  handleInput(char) {
83
107
  this.errorMsg = '';
108
+ // Tab (Accept Suggestion) or Toggle Column
109
+ if (char === '\t') {
110
+ if (this.colIndex === 0 && this.ghost) {
111
+ this.items[this.rowIndex].key += this.ghost;
112
+ this.ghost = '';
113
+ this.render(false);
114
+ return;
115
+ }
116
+ // Standard Tab behavior (toggle column)
117
+ this.colIndex = this.colIndex === 0 ? 1 : 0;
118
+ this.ghost = ''; // Clear ghost when changing columns
119
+ this.render(false);
120
+ return;
121
+ }
84
122
  // Navigation
85
123
  if (this.isUp(char)) {
86
124
  if (this.rowIndex > 0)
87
125
  this.rowIndex--;
126
+ this.ghost = '';
88
127
  this.render(false);
89
128
  return;
90
129
  }
@@ -96,28 +135,28 @@ class MapPrompt extends base_1.Prompt {
96
135
  // Down at last row adds new row
97
136
  this.items.push({ key: '', value: '' });
98
137
  this.rowIndex++;
99
- this.colIndex = 0; // Optional: Reset to Key col when creating new row
138
+ this.colIndex = 0;
100
139
  }
140
+ this.ghost = '';
101
141
  this.render(false);
102
142
  return;
103
143
  }
104
- if (char === '\t' || this.isRight(char) || this.isLeft(char)) {
105
- // Toggle column
144
+ if (this.isRight(char) || this.isLeft(char)) {
106
145
  this.colIndex = this.colIndex === 0 ? 1 : 0;
146
+ this.ghost = '';
107
147
  this.render(false);
108
148
  return;
109
149
  }
110
150
  // CRUD
111
- // Ctrl+N (Standard ASCII for Ctrl+N is \x0e)
112
- if (char === '\x0e') {
151
+ if (char === '\x0e') { // Ctrl+N
113
152
  this.items.push({ key: '', value: '' });
114
153
  this.rowIndex = this.items.length - 1;
115
154
  this.colIndex = 0;
155
+ this.ghost = '';
116
156
  this.render(false);
117
157
  return;
118
158
  }
119
- // Ctrl+D (Standard ASCII for Ctrl+D is \x04)
120
- if (char === '\x04') {
159
+ if (char === '\x04') { // Ctrl+D
121
160
  if (this.items.length > 1) {
122
161
  this.items.splice(this.rowIndex, 1);
123
162
  if (this.rowIndex >= this.items.length) {
@@ -125,9 +164,9 @@ class MapPrompt extends base_1.Prompt {
125
164
  }
126
165
  }
127
166
  else {
128
- // Clear the last remaining item instead of deleting it
129
167
  this.items[0] = { key: '', value: '' };
130
168
  }
169
+ this.ghost = '';
131
170
  this.render(false);
132
171
  return;
133
172
  }
@@ -157,8 +196,13 @@ class MapPrompt extends base_1.Prompt {
157
196
  if (char === '\u0008' || char === '\x7f') {
158
197
  const item = this.items[this.rowIndex];
159
198
  if (this.colIndex === 0) {
160
- if (item.key.length > 0)
199
+ if (item.key.length > 0) {
161
200
  item.key = item.key.slice(0, -1);
201
+ this.updateGhost();
202
+ }
203
+ else {
204
+ this.ghost = '';
205
+ }
162
206
  }
163
207
  else {
164
208
  if (item.value.length > 0)
@@ -172,6 +216,7 @@ class MapPrompt extends base_1.Prompt {
172
216
  const item = this.items[this.rowIndex];
173
217
  if (this.colIndex === 0) {
174
218
  item.key += char;
219
+ this.updateGhost();
175
220
  }
176
221
  else {
177
222
  item.value += char;
@@ -184,12 +229,14 @@ class MapPrompt extends base_1.Prompt {
184
229
  if (event.scroll === 'up') {
185
230
  if (this.rowIndex > 0) {
186
231
  this.rowIndex--;
232
+ this.ghost = '';
187
233
  this.render(false);
188
234
  }
189
235
  }
190
236
  else if (event.scroll === 'down') {
191
237
  if (this.rowIndex < this.items.length - 1) {
192
238
  this.rowIndex++;
239
+ this.ghost = '';
193
240
  this.render(false);
194
241
  }
195
242
  }
package/dist/types.d.ts CHANGED
@@ -236,6 +236,7 @@ export interface CalendarOptions extends BaseOptions {
236
236
  }
237
237
  export interface MapOptions extends BaseOptions {
238
238
  initial?: Record<string, string>;
239
+ suggestions?: string[];
239
240
  }
240
241
  export interface SemVerOptions extends BaseOptions {
241
242
  currentVersion: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mepcli",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Zero-dependency, interactive CLI prompt",
5
5
  "repository": {
6
6
  "type": "git",