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 +8 -359
- package/dist/core.d.ts +2 -3
- package/dist/core.js +2 -3
- package/dist/prompts/curl-utils.d.ts +25 -0
- package/dist/prompts/curl-utils.js +41 -0
- package/dist/prompts/curl.d.ts +8 -7
- package/dist/prompts/curl.js +200 -82
- package/dist/prompts/exec.d.ts +4 -0
- package/dist/prompts/exec.js +59 -5
- package/dist/prompts/map.d.ts +2 -0
- package/dist/prompts/map.js +60 -13
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
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`
|
|
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`
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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;
|
package/dist/prompts/curl.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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;
|
package/dist/prompts/curl.js
CHANGED
|
@@ -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
|
-
|
|
32
|
+
// URL State
|
|
33
|
+
this.urlSegments = [];
|
|
34
|
+
this.urlCursor = 0;
|
|
24
35
|
this.headers = {};
|
|
25
36
|
this.body = '';
|
|
26
|
-
//
|
|
27
|
-
this.urlCursor = 0;
|
|
37
|
+
// Render State
|
|
28
38
|
this.lastLinesUp = 0;
|
|
29
|
-
|
|
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
|
-
|
|
37
|
-
this.
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 +=
|
|
91
|
+
cmd += `${continuation}-H ${strategy.escape(`${k}: ${v}`)}`;
|
|
63
92
|
});
|
|
64
93
|
// Body
|
|
65
94
|
if (this.hasBody && this.body) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
cmd += ` -d "${escapedBody}"`;
|
|
95
|
+
const escapedBody = strategy.escape(this.body);
|
|
96
|
+
cmd += `${continuation}-d ${escapedBody}`;
|
|
69
97
|
}
|
|
70
98
|
// URL
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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 =
|
|
96
|
-
if (
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
|
377
|
+
// Cancelled
|
|
261
378
|
}
|
|
262
379
|
this.resumeInput();
|
|
263
|
-
this.render(
|
|
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(
|
|
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
|
}
|
package/dist/prompts/exec.d.ts
CHANGED
|
@@ -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;
|
package/dist/prompts/exec.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
133
|
+
// Prompt base class handles Ctrl+C (SIGINT) in _onKeyHandler
|
|
134
|
+
// which calls cleanup() -> killChild().
|
|
81
135
|
}
|
|
82
136
|
}
|
|
83
137
|
exports.ExecPrompt = ExecPrompt;
|
package/dist/prompts/map.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/prompts/map.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
60
|
-
|
|
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;
|
|
138
|
+
this.colIndex = 0;
|
|
100
139
|
}
|
|
140
|
+
this.ghost = '';
|
|
101
141
|
this.render(false);
|
|
102
142
|
return;
|
|
103
143
|
}
|
|
104
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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;
|