mepcli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/dist/core.d.ts +964 -7
- package/dist/core.js +964 -7
- 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
|
@@ -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
|
}
|