@ikyyofc/gemini-cli 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +44 -49
- package/package.json +1 -1
- 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,26 +55,31 @@ 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
84
|
process.on("exit", cleanup);
|
|
79
85
|
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
@@ -96,7 +102,7 @@ function showPrompt() {
|
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
// ─────────────────────────────────────────────────────────────────
|
|
99
|
-
//
|
|
105
|
+
// Attach file
|
|
100
106
|
// ─────────────────────────────────────────────────────────────────
|
|
101
107
|
function attachFile(fp) {
|
|
102
108
|
const p = path.resolve(fp.trim().replace(/^['"]|['"]$/g, ""));
|
|
@@ -110,8 +116,10 @@ function attachFile(fp) {
|
|
|
110
116
|
// ─────────────────────────────────────────────────────────────────
|
|
111
117
|
// Send message
|
|
112
118
|
// ─────────────────────────────────────────────────────────────────
|
|
113
|
-
async function send(
|
|
114
|
-
|
|
119
|
+
async function send(rawLine) {
|
|
120
|
+
// Decode \x00 → \n from paste encoding
|
|
121
|
+
const userText = restorePaste(rawLine).trim();
|
|
122
|
+
if (!userText) return;
|
|
115
123
|
|
|
116
124
|
printUser(userText + (pendingFile ? chalk.dim(` [${path.basename(pendingPath)}]`) : ""));
|
|
117
125
|
|
|
@@ -127,7 +135,10 @@ async function send(userText) {
|
|
|
127
135
|
}
|
|
128
136
|
} else {
|
|
129
137
|
const { default: ora } = await import("ora");
|
|
130
|
-
const sp = ora({
|
|
138
|
+
const sp = ora({
|
|
139
|
+
text: "thinking…", spinner: "dots", color: "cyan",
|
|
140
|
+
prefixText: " ", discardStdin: false
|
|
141
|
+
}).start();
|
|
131
142
|
const msgs = [];
|
|
132
143
|
if (sysInstruction()) msgs.push({ role: "system", content: sysInstruction() });
|
|
133
144
|
msgs.push(...history, { role: "user", content: userText });
|
|
@@ -182,7 +193,7 @@ async function handleCommand(input) {
|
|
|
182
193
|
} else if (sub === "install") {
|
|
183
194
|
const src = tokens.slice(2).join(" ");
|
|
184
195
|
if (!src) { printError("usage: /ext install <path-or-url>"); return; }
|
|
185
|
-
printInfo(
|
|
196
|
+
printInfo("installing…");
|
|
186
197
|
const r = await installExtension(src);
|
|
187
198
|
r.error ? printError(r.error) : (printSuccess(`installed: ${r.name}`), reloadAll());
|
|
188
199
|
} else if (sub === "uninstall") {
|
|
@@ -240,7 +251,7 @@ async function handleCommand(input) {
|
|
|
240
251
|
history.forEach((m, i) => {
|
|
241
252
|
const who = m.role === "user" ? chalk.hex("#4A9EFF")("you") : chalk.hex("#4EC9B0")("gemini");
|
|
242
253
|
const body = String(m.content).replace(/\n/g, " ").slice(0, 90);
|
|
243
|
-
console.log(` ${chalk.dim("["+(
|
|
254
|
+
console.log(` ${chalk.dim("["+(i+1)+"]")} ${who} ${chalk.dim(body)}`);
|
|
244
255
|
});
|
|
245
256
|
console.log("");
|
|
246
257
|
break;
|
|
@@ -256,8 +267,7 @@ async function handleCommand(input) {
|
|
|
256
267
|
printSuccess(`exported → ${arg}`);
|
|
257
268
|
break;
|
|
258
269
|
|
|
259
|
-
case "/cwd":
|
|
260
|
-
printInfo(process.cwd()); break;
|
|
270
|
+
case "/cwd": printInfo(process.cwd()); break;
|
|
261
271
|
|
|
262
272
|
case "/cd":
|
|
263
273
|
if (!arg) { printError("usage: /cd <path>"); break; }
|
|
@@ -274,10 +284,7 @@ async function handleCommand(input) {
|
|
|
274
284
|
break;
|
|
275
285
|
|
|
276
286
|
case "/exit": case "/quit":
|
|
277
|
-
cleanup();
|
|
278
|
-
printInfo("bye");
|
|
279
|
-
process.exit(0);
|
|
280
|
-
break;
|
|
287
|
+
cleanup(); console.log(""); process.exit(0); break;
|
|
281
288
|
|
|
282
289
|
default:
|
|
283
290
|
printError(`unknown command: ${cmd} — /help for list`);
|
|
@@ -285,13 +292,12 @@ async function handleCommand(input) {
|
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
// ─────────────────────────────────────────────────────────────────
|
|
288
|
-
// Main
|
|
295
|
+
// Main
|
|
289
296
|
// ─────────────────────────────────────────────────────────────────
|
|
290
297
|
async function main() {
|
|
291
298
|
process.stdout.write("\x1Bc");
|
|
292
299
|
console.log(renderWelcome(memoryLoaded.length, extensions.length));
|
|
293
300
|
|
|
294
|
-
// Parse flags
|
|
295
301
|
const argv = process.argv.slice(2);
|
|
296
302
|
const positional = [];
|
|
297
303
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -303,30 +309,30 @@ async function main() {
|
|
|
303
309
|
else if (!a.startsWith("--")) { positional.push(a); }
|
|
304
310
|
}
|
|
305
311
|
|
|
306
|
-
// One-shot mode
|
|
307
312
|
if (positional.length > 0) {
|
|
308
313
|
await send(positional.join(" "));
|
|
309
|
-
cleanup();
|
|
310
|
-
process.exit(0);
|
|
314
|
+
cleanup(); process.exit(0);
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
showPrompt();
|
|
314
318
|
|
|
315
|
-
rl.on("line", async (
|
|
316
|
-
//
|
|
319
|
+
rl.on("line", async (rawLine) => {
|
|
320
|
+
// Decode paste encoding before doing anything else
|
|
321
|
+
const text = restorePaste(rawLine).trim();
|
|
322
|
+
|
|
323
|
+
if (!text) { showPrompt(); return; }
|
|
324
|
+
|
|
325
|
+
// Don't queue — just warn and re-prompt
|
|
317
326
|
if (processing) {
|
|
318
327
|
printWarning("still thinking…");
|
|
319
328
|
showPrompt();
|
|
320
329
|
return;
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
const text = input.trim();
|
|
324
|
-
if (!text) { showPrompt(); return; }
|
|
325
|
-
|
|
326
332
|
processing = true;
|
|
327
333
|
try {
|
|
328
334
|
if (text.startsWith("/")) await handleCommand(text);
|
|
329
|
-
else await send(
|
|
335
|
+
else await send(rawLine); // send rawLine; send() decodes internally
|
|
330
336
|
} catch (e) {
|
|
331
337
|
printError(e.message);
|
|
332
338
|
} finally {
|
|
@@ -335,14 +341,9 @@ async function main() {
|
|
|
335
341
|
}
|
|
336
342
|
});
|
|
337
343
|
|
|
338
|
-
// 'close'
|
|
339
|
-
rl.on("close", () => {
|
|
340
|
-
cleanup();
|
|
341
|
-
console.log("");
|
|
342
|
-
process.exit(0);
|
|
343
|
-
});
|
|
344
|
+
// readline 'close' = Ctrl+D via our manual handler, or rl.close() call
|
|
345
|
+
rl.on("close", () => { cleanup(); console.log(""); process.exit(0); });
|
|
344
346
|
|
|
345
|
-
// SIGINT (Ctrl+C) — cancel current operation or exit
|
|
346
347
|
rl.on("SIGINT", () => {
|
|
347
348
|
if (processing) {
|
|
348
349
|
process.stdout.write("\n");
|
|
@@ -350,14 +351,8 @@ async function main() {
|
|
|
350
351
|
showPrompt();
|
|
351
352
|
return;
|
|
352
353
|
}
|
|
353
|
-
|
|
354
|
-
cleanup();
|
|
355
|
-
console.log("");
|
|
356
|
-
process.exit(0);
|
|
354
|
+
cleanup(); console.log(""); process.exit(0);
|
|
357
355
|
});
|
|
358
356
|
}
|
|
359
357
|
|
|
360
|
-
main().catch(e => {
|
|
361
|
-
console.error(chalk.red("fatal:"), e.message);
|
|
362
|
-
process.exit(1);
|
|
363
|
-
});
|
|
358
|
+
main().catch(e => { console.error(chalk.red("fatal:"), e.message); process.exit(1); });
|
package/package.json
CHANGED
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"); }
|