@ikyyofc/gemini-cli 1.0.4 → 1.0.6
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/index.js +53 -53
- package/package.json +1 -1
- package/src/agent.js +45 -46
- package/src/input.js +86 -50
package/index.js
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
printError, printInfo, printSuccess, printWarning
|
|
20
20
|
} from "./src/renderer.js";
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
PasteTransform, restorePaste,
|
|
23
|
+
enableBracketedPaste, disableBracketedPaste
|
|
23
24
|
} from "./src/input.js";
|
|
24
25
|
|
|
25
26
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -54,34 +55,46 @@ function sysInstruction() {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// ─────────────────────────────────────────────────────────────────
|
|
57
|
-
// Readline —
|
|
58
|
+
// Readline — stdin → PasteTransform → readline
|
|
59
|
+
// { end: false } prevents Ctrl+D on stdin from auto-closing readline
|
|
58
60
|
// ─────────────────────────────────────────────────────────────────
|
|
61
|
+
enableBracketedPaste();
|
|
62
|
+
|
|
63
|
+
const pasteStream = new PasteTransform();
|
|
64
|
+
process.stdin.pipe(pasteStream, { end: false });
|
|
65
|
+
|
|
66
|
+
// Handle Ctrl+D (stdin end) manually
|
|
67
|
+
process.stdin.once("end", () => {
|
|
68
|
+
cleanup();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
59
72
|
const rl = readline.createInterface({
|
|
60
|
-
input:
|
|
73
|
+
input: pasteStream,
|
|
61
74
|
output: process.stdout,
|
|
62
75
|
terminal: true,
|
|
63
76
|
historySize: 200,
|
|
64
77
|
crlfDelay: Infinity,
|
|
65
78
|
});
|
|
66
79
|
|
|
67
|
-
// Attach paste interception (keypress-based, not Transform-based)
|
|
68
|
-
enableBracketedPaste();
|
|
69
|
-
setupPaste(rl);
|
|
70
|
-
|
|
71
|
-
// Clean up on any exit
|
|
72
80
|
function cleanup() {
|
|
73
81
|
disableBracketedPaste();
|
|
74
|
-
if (process.stdin.isTTY) {
|
|
75
|
-
try { process.stdin.setRawMode(false); } catch {}
|
|
76
|
-
}
|
|
82
|
+
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch {}
|
|
77
83
|
}
|
|
78
|
-
process.on("exit",
|
|
84
|
+
process.on("exit", cleanup);
|
|
79
85
|
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
80
86
|
|
|
87
|
+
// When terminal is resized (or focus returns on some terminals),
|
|
88
|
+
// only redraw prompt if we are NOT currently processing
|
|
89
|
+
process.on("SIGWINCH", () => { if (!processing) showPrompt(); });
|
|
90
|
+
|
|
81
91
|
// ─────────────────────────────────────────────────────────────────
|
|
82
92
|
// Prompt
|
|
83
93
|
// ─────────────────────────────────────────────────────────────────
|
|
84
94
|
function showPrompt() {
|
|
95
|
+
// Never redraw prompt while a request is in flight
|
|
96
|
+
if (processing) return;
|
|
97
|
+
|
|
85
98
|
const mode = agentMode ? chalk.hex("#4EC9B0")("agent") : chalk.dim("chat");
|
|
86
99
|
const yolo = autoApprove ? chalk.red(" yolo") : "";
|
|
87
100
|
const file = pendingFile ? chalk.yellow(` +${path.basename(pendingPath)}`) : "";
|
|
@@ -96,7 +109,7 @@ function showPrompt() {
|
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
// ─────────────────────────────────────────────────────────────────
|
|
99
|
-
//
|
|
112
|
+
// Attach file
|
|
100
113
|
// ─────────────────────────────────────────────────────────────────
|
|
101
114
|
function attachFile(fp) {
|
|
102
115
|
const p = path.resolve(fp.trim().replace(/^['"]|['"]$/g, ""));
|
|
@@ -110,8 +123,10 @@ function attachFile(fp) {
|
|
|
110
123
|
// ─────────────────────────────────────────────────────────────────
|
|
111
124
|
// Send message
|
|
112
125
|
// ─────────────────────────────────────────────────────────────────
|
|
113
|
-
async function send(
|
|
114
|
-
|
|
126
|
+
async function send(rawLine) {
|
|
127
|
+
// Decode \x00 → \n from paste encoding
|
|
128
|
+
const userText = restorePaste(rawLine).trim();
|
|
129
|
+
if (!userText) return;
|
|
115
130
|
|
|
116
131
|
printUser(userText + (pendingFile ? chalk.dim(` [${path.basename(pendingPath)}]`) : ""));
|
|
117
132
|
|
|
@@ -126,19 +141,20 @@ async function send(userText) {
|
|
|
126
141
|
history.push({ role: "assistant", content: res.finalResponse });
|
|
127
142
|
}
|
|
128
143
|
} else {
|
|
129
|
-
|
|
130
|
-
const sp = ora({ text: "thinking…", spinner: "dots", color: "cyan", prefixText: " ", discardStdin: false }).start();
|
|
144
|
+
process.stdout.write(chalk.hex("#555566")(" ⋯ ") + chalk.dim("thinking…") + "\n");
|
|
131
145
|
const msgs = [];
|
|
132
146
|
if (sysInstruction()) msgs.push({ role: "system", content: sysInstruction() });
|
|
133
147
|
msgs.push(...history, { role: "user", content: userText });
|
|
134
148
|
try {
|
|
135
149
|
const t0 = Date.now();
|
|
136
150
|
const reply = await chat(msgs, pendingFile || null);
|
|
137
|
-
|
|
151
|
+
// clear "thinking…" line, print done
|
|
152
|
+
if (process.stdout.isTTY) process.stdout.write("\x1b[1A\x1b[2K");
|
|
153
|
+
process.stdout.write(chalk.hex("#555566")(" ⋯ ") + chalk.dim(`done (${((Date.now()-t0)/1000).toFixed(1)}s)`) + "\n");
|
|
138
154
|
history.push({ role: "user", content: userText }, { role: "assistant", content: reply });
|
|
139
155
|
printAssistant(reply);
|
|
140
156
|
} catch (e) {
|
|
141
|
-
|
|
157
|
+
if (process.stdout.isTTY) process.stdout.write("\x1b[1A\x1b[2K");
|
|
142
158
|
printError(e.message);
|
|
143
159
|
}
|
|
144
160
|
}
|
|
@@ -182,7 +198,7 @@ async function handleCommand(input) {
|
|
|
182
198
|
} else if (sub === "install") {
|
|
183
199
|
const src = tokens.slice(2).join(" ");
|
|
184
200
|
if (!src) { printError("usage: /ext install <path-or-url>"); return; }
|
|
185
|
-
printInfo(
|
|
201
|
+
printInfo("installing…");
|
|
186
202
|
const r = await installExtension(src);
|
|
187
203
|
r.error ? printError(r.error) : (printSuccess(`installed: ${r.name}`), reloadAll());
|
|
188
204
|
} else if (sub === "uninstall") {
|
|
@@ -240,7 +256,7 @@ async function handleCommand(input) {
|
|
|
240
256
|
history.forEach((m, i) => {
|
|
241
257
|
const who = m.role === "user" ? chalk.hex("#4A9EFF")("you") : chalk.hex("#4EC9B0")("gemini");
|
|
242
258
|
const body = String(m.content).replace(/\n/g, " ").slice(0, 90);
|
|
243
|
-
console.log(` ${chalk.dim("["+(
|
|
259
|
+
console.log(` ${chalk.dim("["+(i+1)+"]")} ${who} ${chalk.dim(body)}`);
|
|
244
260
|
});
|
|
245
261
|
console.log("");
|
|
246
262
|
break;
|
|
@@ -256,8 +272,7 @@ async function handleCommand(input) {
|
|
|
256
272
|
printSuccess(`exported → ${arg}`);
|
|
257
273
|
break;
|
|
258
274
|
|
|
259
|
-
case "/cwd":
|
|
260
|
-
printInfo(process.cwd()); break;
|
|
275
|
+
case "/cwd": printInfo(process.cwd()); break;
|
|
261
276
|
|
|
262
277
|
case "/cd":
|
|
263
278
|
if (!arg) { printError("usage: /cd <path>"); break; }
|
|
@@ -274,10 +289,7 @@ async function handleCommand(input) {
|
|
|
274
289
|
break;
|
|
275
290
|
|
|
276
291
|
case "/exit": case "/quit":
|
|
277
|
-
cleanup();
|
|
278
|
-
printInfo("bye");
|
|
279
|
-
process.exit(0);
|
|
280
|
-
break;
|
|
292
|
+
cleanup(); console.log(""); process.exit(0); break;
|
|
281
293
|
|
|
282
294
|
default:
|
|
283
295
|
printError(`unknown command: ${cmd} — /help for list`);
|
|
@@ -285,13 +297,12 @@ async function handleCommand(input) {
|
|
|
285
297
|
}
|
|
286
298
|
|
|
287
299
|
// ─────────────────────────────────────────────────────────────────
|
|
288
|
-
// Main
|
|
300
|
+
// Main
|
|
289
301
|
// ─────────────────────────────────────────────────────────────────
|
|
290
302
|
async function main() {
|
|
291
303
|
process.stdout.write("\x1Bc");
|
|
292
304
|
console.log(renderWelcome(memoryLoaded.length, extensions.length));
|
|
293
305
|
|
|
294
|
-
// Parse flags
|
|
295
306
|
const argv = process.argv.slice(2);
|
|
296
307
|
const positional = [];
|
|
297
308
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -303,30 +314,30 @@ async function main() {
|
|
|
303
314
|
else if (!a.startsWith("--")) { positional.push(a); }
|
|
304
315
|
}
|
|
305
316
|
|
|
306
|
-
// One-shot mode
|
|
307
317
|
if (positional.length > 0) {
|
|
308
318
|
await send(positional.join(" "));
|
|
309
|
-
cleanup();
|
|
310
|
-
process.exit(0);
|
|
319
|
+
cleanup(); process.exit(0);
|
|
311
320
|
}
|
|
312
321
|
|
|
313
322
|
showPrompt();
|
|
314
323
|
|
|
315
|
-
rl.on("line", async (
|
|
316
|
-
//
|
|
324
|
+
rl.on("line", async (rawLine) => {
|
|
325
|
+
// Decode paste encoding before doing anything else
|
|
326
|
+
const text = restorePaste(rawLine).trim();
|
|
327
|
+
|
|
328
|
+
if (!text) { showPrompt(); return; }
|
|
329
|
+
|
|
330
|
+
// Don't queue — just warn and re-prompt
|
|
317
331
|
if (processing) {
|
|
318
332
|
printWarning("still thinking…");
|
|
319
333
|
showPrompt();
|
|
320
334
|
return;
|
|
321
335
|
}
|
|
322
336
|
|
|
323
|
-
const text = input.trim();
|
|
324
|
-
if (!text) { showPrompt(); return; }
|
|
325
|
-
|
|
326
337
|
processing = true;
|
|
327
338
|
try {
|
|
328
339
|
if (text.startsWith("/")) await handleCommand(text);
|
|
329
|
-
else await send(
|
|
340
|
+
else await send(rawLine); // send rawLine; send() decodes internally
|
|
330
341
|
} catch (e) {
|
|
331
342
|
printError(e.message);
|
|
332
343
|
} finally {
|
|
@@ -335,14 +346,9 @@ async function main() {
|
|
|
335
346
|
}
|
|
336
347
|
});
|
|
337
348
|
|
|
338
|
-
// 'close'
|
|
339
|
-
rl.on("close", () => {
|
|
340
|
-
cleanup();
|
|
341
|
-
console.log("");
|
|
342
|
-
process.exit(0);
|
|
343
|
-
});
|
|
349
|
+
// readline 'close' = Ctrl+D via our manual handler, or rl.close() call
|
|
350
|
+
rl.on("close", () => { cleanup(); console.log(""); process.exit(0); });
|
|
344
351
|
|
|
345
|
-
// SIGINT (Ctrl+C) — cancel current operation or exit
|
|
346
352
|
rl.on("SIGINT", () => {
|
|
347
353
|
if (processing) {
|
|
348
354
|
process.stdout.write("\n");
|
|
@@ -350,14 +356,8 @@ async function main() {
|
|
|
350
356
|
showPrompt();
|
|
351
357
|
return;
|
|
352
358
|
}
|
|
353
|
-
|
|
354
|
-
cleanup();
|
|
355
|
-
console.log("");
|
|
356
|
-
process.exit(0);
|
|
359
|
+
cleanup(); console.log(""); process.exit(0);
|
|
357
360
|
});
|
|
358
361
|
}
|
|
359
362
|
|
|
360
|
-
main().catch(e => {
|
|
361
|
-
console.error(chalk.red("fatal:"), e.message);
|
|
362
|
-
process.exit(1);
|
|
363
|
-
});
|
|
363
|
+
main().catch(e => { console.error(chalk.red("fatal:"), e.message); process.exit(1); });
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,116 +1,115 @@
|
|
|
1
|
-
// src/agent.js — ReAct agent loop
|
|
1
|
+
// src/agent.js — ReAct agent loop, native Gemini function calling
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
3
|
import { callGemini } from "./gemini.js";
|
|
5
4
|
import { GEMINI_TOOLS, executeTool } from "./tools.js";
|
|
6
5
|
import { printAssistant, printError, printWarning, printToolCall, printToolResult } from "./renderer.js";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8
|
+
// Static progress line — write once, no continuous redraw
|
|
9
|
+
// Safe when terminal loses focus (no ANSI overwrite loops)
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────
|
|
11
|
+
function printStep(n, label) {
|
|
12
|
+
process.stdout.write(
|
|
13
|
+
chalk.hex("#555566")(" ⋯ ") +
|
|
14
|
+
chalk.dim(label) +
|
|
15
|
+
(n > 1 ? chalk.hex("#555566")(` (step ${n})`) : "") +
|
|
16
|
+
"\n"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function clearLastLine() {
|
|
21
|
+
// Move cursor up one line and clear it — only called right after printStep
|
|
22
|
+
// so we can replace "thinking…" with the actual tool section header
|
|
23
|
+
if (process.stdout.isTTY) {
|
|
24
|
+
process.stdout.write("\x1b[1A\x1b[2K");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
export async function runAgentLoop(userMessage, history, {
|
|
19
29
|
systemInstruction = null,
|
|
20
30
|
autoApprove = false,
|
|
21
31
|
maxIterations = 40,
|
|
22
32
|
} = {}) {
|
|
23
33
|
|
|
24
|
-
// Build initial message list
|
|
25
34
|
const messages = [
|
|
26
35
|
...history,
|
|
27
36
|
{ role: "user", content: userMessage }
|
|
28
37
|
];
|
|
29
38
|
|
|
30
|
-
let iteration
|
|
39
|
+
let iteration = 0;
|
|
40
|
+
let sectionOpen = false;
|
|
31
41
|
|
|
32
42
|
while (iteration < maxIterations) {
|
|
33
43
|
iteration++;
|
|
34
44
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
text: chalk.dim(iteration === 1 ? "thinking…" : `step ${iteration}…`),
|
|
38
|
-
spinner: "dots",
|
|
39
|
-
color: "cyan",
|
|
40
|
-
prefixText: " ",
|
|
41
|
-
discardStdin: false, // prevent ora from pausing stdin after stop
|
|
42
|
-
}).start();
|
|
45
|
+
// Print a static "thinking…" line — written once, not looping
|
|
46
|
+
printStep(iteration, iteration === 1 ? "thinking…" : "thinking…");
|
|
43
47
|
|
|
44
48
|
let parts;
|
|
45
49
|
try {
|
|
46
50
|
const res = await callGemini({ messages, tools: GEMINI_TOOLS, systemInstruction });
|
|
47
51
|
parts = res.parts;
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
// Clear the "thinking…" line now that we have a response
|
|
53
|
+
clearLastLine();
|
|
50
54
|
} catch (err) {
|
|
51
|
-
|
|
55
|
+
clearLastLine();
|
|
52
56
|
printError(err.message);
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
const textParts = parts.filter(p => p.text != null);
|
|
60
|
+
const textParts = parts.filter(p => p.text != null);
|
|
58
61
|
const callParts = parts.filter(p => p.functionCall != null);
|
|
59
62
|
|
|
60
|
-
//
|
|
63
|
+
// Model thinking aloud alongside tool calls
|
|
61
64
|
const textContent = textParts.map(p => p.text).join("").trim();
|
|
62
65
|
if (textContent && callParts.length > 0) {
|
|
63
66
|
process.stdout.write(
|
|
64
|
-
chalk.
|
|
67
|
+
chalk.hex("#555566")(" ┄ ") +
|
|
68
|
+
chalk.dim(textContent.split("\n")[0].slice(0, 80)) +
|
|
69
|
+
"\n"
|
|
65
70
|
);
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
// ──
|
|
73
|
+
// ── Final answer (no more tool calls) ─────────────────────
|
|
69
74
|
if (callParts.length === 0) {
|
|
70
75
|
const final = textParts.map(p => p.text).join("").trim();
|
|
71
|
-
|
|
72
|
-
if (iteration > 1) {
|
|
76
|
+
if (sectionOpen) {
|
|
73
77
|
process.stdout.write(
|
|
74
78
|
chalk.hex("#4A9EFF")(" ╰") +
|
|
75
79
|
chalk.hex("#555566")("─".repeat(47)) + "\n"
|
|
76
80
|
);
|
|
81
|
+
sectionOpen = false;
|
|
77
82
|
}
|
|
78
83
|
if (final) printAssistant(final);
|
|
79
84
|
return { finalResponse: final, iterations: iteration };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
// ──
|
|
87
|
+
// ── Tool calls ─────────────────────────────────────────────
|
|
83
88
|
messages.push({ role: "model", parts });
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
if (iteration === 1) {
|
|
90
|
+
if (!sectionOpen) {
|
|
87
91
|
process.stdout.write(
|
|
88
92
|
"\n" + chalk.hex("#4A9EFF")(" ╭─ working") +
|
|
89
93
|
chalk.hex("#555566")(" " + "─".repeat(36)) + "\n"
|
|
90
94
|
);
|
|
95
|
+
sectionOpen = true;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
const responseParts = [];
|
|
94
|
-
|
|
95
99
|
for (const part of callParts) {
|
|
96
100
|
const { name, args } = part.functionCall;
|
|
97
101
|
printToolCall(name, args);
|
|
98
|
-
|
|
99
102
|
const result = await executeTool(name, args ?? {}, { autoApprove });
|
|
100
103
|
printToolResult(result);
|
|
101
|
-
|
|
102
|
-
responseParts.push({
|
|
103
|
-
functionResponse: { name, response: result }
|
|
104
|
-
});
|
|
104
|
+
responseParts.push({ functionResponse: { name, response: result } });
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
messages.push({ role: "user", parts: responseParts });
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
if (sectionOpen) {
|
|
111
|
+
process.stdout.write(chalk.hex("#4A9EFF")(" ╰") + chalk.hex("#555566")("─".repeat(47)) + "\n");
|
|
112
|
+
}
|
|
113
|
+
printWarning(`max iterations (${maxIterations}) reached`);
|
|
111
114
|
return { finalResponse: null, iterations: iteration };
|
|
112
115
|
}
|
|
113
|
-
|
|
114
|
-
function clearLine() {
|
|
115
|
-
if (process.stdout.clearLine) { process.stdout.clearLine(0); process.stdout.cursorTo(0); }
|
|
116
|
-
}
|
package/src/input.js
CHANGED
|
@@ -1,60 +1,96 @@
|
|
|
1
|
-
// src/input.js — Bracketed paste via
|
|
1
|
+
// src/input.js — Bracketed paste via Transform stream (byte-level interception)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Keypress-based approach is unreliable because keypress events for \x1b[200~
|
|
4
|
+
// may arrive AFTER readline has already fired 'line' events for the pasted lines.
|
|
5
|
+
// Byte-level interception via Transform is the only reliable approach.
|
|
6
6
|
//
|
|
7
|
-
import
|
|
7
|
+
import { Transform } from "stream";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const PASTE_START = "\x1b[200~";
|
|
10
|
+
const PASTE_END = "\x1b[201~";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let pasting = false;
|
|
21
|
-
let pasteBuf = [];
|
|
22
|
-
|
|
23
|
-
// Watch for paste start/end escape sequences
|
|
24
|
-
const onKeypress = (_str, key) => {
|
|
25
|
-
if (!key) return;
|
|
26
|
-
const seq = key.sequence ?? "";
|
|
27
|
-
|
|
28
|
-
if (seq === "\x1b[200~") {
|
|
29
|
-
pasting = true;
|
|
30
|
-
pasteBuf = [];
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
12
|
+
export class PasteTransform extends Transform {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this._buf = ""; // raw accumulator
|
|
16
|
+
this._pasting = false;
|
|
17
|
+
this._pasteBuf = ""; // paste content accumulator
|
|
18
|
+
}
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
20
|
+
_transform(chunk, _enc, cb) {
|
|
21
|
+
this._buf += chunk.toString("utf8");
|
|
22
|
+
|
|
23
|
+
let output = "";
|
|
24
|
+
|
|
25
|
+
while (this._buf.length > 0) {
|
|
26
|
+
|
|
27
|
+
if (!this._pasting) {
|
|
28
|
+
const si = this._buf.indexOf(PASTE_START);
|
|
29
|
+
|
|
30
|
+
if (si === -1) {
|
|
31
|
+
// No paste start found — check for partial escape at tail
|
|
32
|
+
const esc = this._buf.lastIndexOf("\x1b");
|
|
33
|
+
if (esc !== -1 && (this._buf.length - esc) < PASTE_START.length) {
|
|
34
|
+
// Could be partial escape sequence — flush safe part, hold rest
|
|
35
|
+
output += this._buf.slice(0, esc);
|
|
36
|
+
this._buf = this._buf.slice(esc);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
// No escape at all, pass through
|
|
40
|
+
output += this._buf;
|
|
41
|
+
this._buf = "";
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Found paste start
|
|
46
|
+
output += this._buf.slice(0, si); // pass through text before it
|
|
47
|
+
this._buf = this._buf.slice(si + PASTE_START.length);
|
|
48
|
+
this._pasting = true;
|
|
49
|
+
this._pasteBuf = "";
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
} else {
|
|
52
|
+
// Inside paste — look for end marker
|
|
53
|
+
const ei = this._buf.indexOf(PASTE_END);
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
if (ei === -1) {
|
|
56
|
+
// End not here yet — buffer everything and wait
|
|
57
|
+
this._pasteBuf += this._buf;
|
|
58
|
+
this._buf = "";
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Found paste end
|
|
63
|
+
this._pasteBuf += this._buf.slice(0, ei);
|
|
64
|
+
this._buf = this._buf.slice(ei + PASTE_END.length);
|
|
65
|
+
this._pasting = false;
|
|
66
|
+
|
|
67
|
+
// Replace newlines with \x00 so readline treats whole paste as ONE line
|
|
68
|
+
const encoded = this._pasteBuf
|
|
69
|
+
.replace(/\r\n/g, "\x00")
|
|
70
|
+
.replace(/\r/g, "\x00")
|
|
71
|
+
.replace(/\n/g, "\x00");
|
|
72
|
+
|
|
73
|
+
output += encoded + "\n"; // \n makes readline fire one 'line' event
|
|
74
|
+
this._pasteBuf = "";
|
|
75
|
+
}
|
|
52
76
|
}
|
|
53
|
-
return _emit(event, ...args);
|
|
54
|
-
};
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
if (output) this.push(output);
|
|
79
|
+
cb();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_flush(cb) {
|
|
83
|
+
// Flush anything remaining
|
|
84
|
+
if (this._buf) this.push(this._buf);
|
|
85
|
+
if (this._pasteBuf) this.push(this._pasteBuf);
|
|
86
|
+
cb();
|
|
87
|
+
}
|
|
60
88
|
}
|
|
89
|
+
|
|
90
|
+
/** Decode: \x00 → \n (undo the encoding done above) */
|
|
91
|
+
export function restorePaste(line) {
|
|
92
|
+
return line.replace(/\x00/g, "\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function enableBracketedPaste() { process.stdout.write("\x1b[?2004h"); }
|
|
96
|
+
export function disableBracketedPaste() { process.stdout.write("\x1b[?2004l"); }
|