@oxecli/oxe 1.0.68 → 1.0.70
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/dist/cli.js +13 -13
- package/dist/engine.js +2 -2
- package/dist/tools.js +3 -3
- package/dist/ui.js +29 -361
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -88,7 +88,7 @@ export class CLI {
|
|
|
88
88
|
const rows = COMMAND_HELP.map(([cmd, desc]) => {
|
|
89
89
|
const styled = cmd.replace(/(<[^>]+>)/g, "\x1b[36m$1\x1b[0m");
|
|
90
90
|
return {
|
|
91
|
-
cells: [`\x1b[1;36m${styled}\x1b[0m`, `\x1b[
|
|
91
|
+
cells: [`\x1b[1;36m${styled}\x1b[0m`, `\x1b[90m${desc}\x1b[0m`],
|
|
92
92
|
};
|
|
93
93
|
});
|
|
94
94
|
const panel = renderTableString(rows, {
|
|
@@ -181,7 +181,7 @@ export class CLI {
|
|
|
181
181
|
}
|
|
182
182
|
if (renders.length > maxItems) {
|
|
183
183
|
const skipped = renders.length - maxItems;
|
|
184
|
-
process.stdout.write(`\x1b[
|
|
184
|
+
process.stdout.write(`\x1b[90m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
|
|
185
185
|
renders.splice(0, renders.length - maxItems);
|
|
186
186
|
}
|
|
187
187
|
const last = renders.length - 1;
|
|
@@ -212,7 +212,7 @@ export class CLI {
|
|
|
212
212
|
let entries = story.filter((e) => String(e["text"] ?? "").trim());
|
|
213
213
|
if (entries.length > maxItems) {
|
|
214
214
|
const skipped = entries.length - maxItems;
|
|
215
|
-
process.stdout.write(`\x1b[
|
|
215
|
+
process.stdout.write(`\x1b[90m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
|
|
216
216
|
entries = entries.slice(-maxItems);
|
|
217
217
|
}
|
|
218
218
|
const last = entries.length - 1;
|
|
@@ -247,7 +247,7 @@ export class CLI {
|
|
|
247
247
|
const label = truncateLabel(rec["label"]);
|
|
248
248
|
const items = rec["input_items"] ?? [];
|
|
249
249
|
const toks = estimateTokens(items).toLocaleString();
|
|
250
|
-
return `${sid.padStart(4)}\x1b[
|
|
250
|
+
return `${sid.padStart(4)}\x1b[90m ${updated}\x1b[0m ${label}\x1b[90m ${items.length} items · ${toks} tok\x1b[0m`;
|
|
251
251
|
}
|
|
252
252
|
showConversationsPlain(recs) {
|
|
253
253
|
process.stdout.write("\n");
|
|
@@ -273,9 +273,9 @@ export class CLI {
|
|
|
273
273
|
const toks = estimateTokens(items).toLocaleString();
|
|
274
274
|
const selectedRow = idx === selected;
|
|
275
275
|
const content = `${selectedRow ? "\x1b[1;37m" : "\x1b[1m"}${sid.padStart(4)}\x1b[0m` +
|
|
276
|
-
`${selectedRow ? "\x1b[2;36m" : "\x1b[
|
|
276
|
+
`${selectedRow ? "\x1b[2;36m" : "\x1b[90m"} ${updated}\x1b[0m` +
|
|
277
277
|
`${selectedRow ? " \x1b[1;37m" : " "}${label}\x1b[0m` +
|
|
278
|
-
`${selectedRow ? "\x1b[2;36m" : "\x1b[
|
|
278
|
+
`${selectedRow ? "\x1b[2;36m" : "\x1b[90m"} ${items.length} items · ${toks} tok\x1b[0m`;
|
|
279
279
|
rows.push({
|
|
280
280
|
cells: [selectedRow ? "\x1b[1;36m❯\x1b[0m" : "", content],
|
|
281
281
|
});
|
|
@@ -283,11 +283,11 @@ export class CLI {
|
|
|
283
283
|
const below = total - hi;
|
|
284
284
|
let footer = "";
|
|
285
285
|
if (lo > 0 && below > 0)
|
|
286
|
-
footer = `\x1b[
|
|
286
|
+
footer = `\x1b[90m▲ ${lo} earlier · ▼ ${below} more\x1b[0m`;
|
|
287
287
|
else if (lo > 0)
|
|
288
|
-
footer = `\x1b[
|
|
288
|
+
footer = `\x1b[90m▲ ${lo} earlier\x1b[0m`;
|
|
289
289
|
else if (below > 0)
|
|
290
|
-
footer = `\x1b[
|
|
290
|
+
footer = `\x1b[90m▼ ${below} more\x1b[0m`;
|
|
291
291
|
if (footer)
|
|
292
292
|
rows.push({ cells: ["", footer] });
|
|
293
293
|
const title = `Conversations \x1b[1;36m${lo + 1}-${hi}/${total}\x1b[0m (↑/↓ · Quit: q · Enter to select)`;
|
|
@@ -372,8 +372,8 @@ export class CLI {
|
|
|
372
372
|
clearScreen();
|
|
373
373
|
this.renderHeader(engine.modelName);
|
|
374
374
|
process.stdout.write("\n");
|
|
375
|
-
process.stdout.write(`\x1b[
|
|
376
|
-
process.stdout.write(`\x1b[
|
|
375
|
+
process.stdout.write(`\x1b[90mResumed:\x1b[0m \x1b[1m${truncateLabel(rec["label"])}\x1b[0m\n`);
|
|
376
|
+
process.stdout.write(`\x1b[90m(${this.inputItems.length} items · ${estimateTokens(this.inputItems).toLocaleString()} tokens)\x1b[0m\n\n`);
|
|
377
377
|
renderPanel("Conversation history restored", "Restored");
|
|
378
378
|
process.stdout.write("\n");
|
|
379
379
|
if (this.story.length)
|
|
@@ -430,7 +430,7 @@ export class CLI {
|
|
|
430
430
|
const effort = parts[1]?.toLowerCase() ?? "";
|
|
431
431
|
if (["none", "low", "high"].includes(effort)) {
|
|
432
432
|
engine.reasoningEffort = effort;
|
|
433
|
-
process.stdout.write(`\n\x1b[32m✓\x1b[0m \x1b[
|
|
433
|
+
process.stdout.write(`\n\x1b[32m✓\x1b[0m \x1b[90mReasoning effort set to \x1b[1;36m${effort}\x1b[0m\x1b[90m.\x1b[0m\n`);
|
|
434
434
|
}
|
|
435
435
|
else {
|
|
436
436
|
process.stdout.write("\n");
|
|
@@ -477,7 +477,7 @@ export class CLI {
|
|
|
477
477
|
}
|
|
478
478
|
this.interruptPending = true;
|
|
479
479
|
process.stdout.write("\n");
|
|
480
|
-
process.stdout.write("\x1b[
|
|
480
|
+
process.stdout.write("\x1b[90mInterrupted agent. Press Ctrl+C again to exit, or type a new prompt.\x1b[0m\n");
|
|
481
481
|
continue;
|
|
482
482
|
}
|
|
483
483
|
throw err;
|
package/dist/engine.js
CHANGED
|
@@ -451,9 +451,9 @@ export class InferenceEngine {
|
|
|
451
451
|
}
|
|
452
452
|
parts.push(`Worked for ${tickDuration((Date.now() - queryStart) / 1000)}`);
|
|
453
453
|
if (stats["tokens"] > 0) {
|
|
454
|
-
parts.push(`Used \x1b[22;37m${stats["tokens"].toLocaleString()}\x1b[
|
|
454
|
+
parts.push(`Used \x1b[22;37m${stats["tokens"].toLocaleString()}\x1b[90m tokens`);
|
|
455
455
|
}
|
|
456
|
-
return `\x1b[
|
|
456
|
+
return `\x1b[90m(${parts.join(" · ")})\x1b[0m`;
|
|
457
457
|
};
|
|
458
458
|
const summary = () => mutedMarkdown(footerText());
|
|
459
459
|
const attachFooter = (items) => {
|
package/dist/tools.js
CHANGED
|
@@ -42,7 +42,7 @@ export function flushPendingDiffOutput() {
|
|
|
42
42
|
}
|
|
43
43
|
function displayDiff(pathName, oldContent, newContent) {
|
|
44
44
|
if (oldContent.length + newContent.length > max_diff_source_chars) {
|
|
45
|
-
pendingDiffOutput.push(`\x1b[
|
|
45
|
+
pendingDiffOutput.push(`\x1b[90m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
|
|
46
46
|
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
@@ -118,7 +118,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
118
118
|
let line;
|
|
119
119
|
if (v.kind === "~") {
|
|
120
120
|
const body = truncateStyled(v.body, innerW - 2);
|
|
121
|
-
line = `\x1b[
|
|
121
|
+
line = `\x1b[90m ${body}\x1b[0m`;
|
|
122
122
|
}
|
|
123
123
|
else if (v.kind === "+") {
|
|
124
124
|
const body = truncateStyled(v.body, innerW - 2);
|
|
@@ -130,7 +130,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
130
130
|
}
|
|
131
131
|
else {
|
|
132
132
|
const body = truncateStyled(v.body, innerW - 2);
|
|
133
|
-
line = `\x1b[
|
|
133
|
+
line = `\x1b[90m ${body}\x1b[0m`;
|
|
134
134
|
}
|
|
135
135
|
const fill = Math.max(0, innerW - plainLen(line));
|
|
136
136
|
pendingDiffOutput.push(`\x1b[90m│\x1b[0m ${line}${" ".repeat(fill)} \x1b[90m│\x1b[0m`);
|
package/dist/ui.js
CHANGED
|
@@ -9,7 +9,6 @@ export function enableRawStdin() {
|
|
|
9
9
|
readline.emitKeypressEvents(process.stdin);
|
|
10
10
|
if (process.stdin.isTTY) {
|
|
11
11
|
process.stdin.setRawMode(true);
|
|
12
|
-
process.stdout.write("\x1b[?9001h\x1b[>1u");
|
|
13
12
|
}
|
|
14
13
|
process.stdin.resume();
|
|
15
14
|
hideCursor();
|
|
@@ -101,24 +100,15 @@ export function truncateStyled(text, maxVisible) {
|
|
|
101
100
|
// ---------------------------------------------------------------------------
|
|
102
101
|
const TAG_MAP = {
|
|
103
102
|
bold: "\x1b[1m",
|
|
104
|
-
dim: "\x1b[
|
|
103
|
+
dim: "\x1b[90m",
|
|
105
104
|
italic: "\x1b[3m",
|
|
106
105
|
underline: "\x1b[4m",
|
|
107
106
|
red: "\x1b[31m",
|
|
108
107
|
green: "\x1b[32m",
|
|
109
108
|
yellow: "\x1b[33m",
|
|
110
|
-
blue: "\x1b[34m",
|
|
111
|
-
magenta: "\x1b[35m",
|
|
112
109
|
cyan: "\x1b[36m",
|
|
113
110
|
white: "\x1b[37m",
|
|
114
111
|
bright_black: "\x1b[90m",
|
|
115
|
-
bright_red: "\x1b[91m",
|
|
116
|
-
bright_green: "\x1b[92m",
|
|
117
|
-
bright_yellow: "\x1b[93m",
|
|
118
|
-
bright_blue: "\x1b[94m",
|
|
119
|
-
bright_magenta: "\x1b[95m",
|
|
120
|
-
bright_cyan: "\x1b[96m",
|
|
121
|
-
bright_white: "\x1b[97m",
|
|
122
112
|
};
|
|
123
113
|
const RESET = "\x1b[0m";
|
|
124
114
|
const MARKUP_RE = /\[(\/?)([a-z0-9_ ]+)\]/gi;
|
|
@@ -167,15 +157,13 @@ function highlightCodeLine(line, _lang = "") {
|
|
|
167
157
|
if (!line)
|
|
168
158
|
return "";
|
|
169
159
|
if (/^\s*(\/\/|#|\/\*)/.test(line)) {
|
|
170
|
-
return `\x1b[
|
|
160
|
+
return `\x1b[90m${line}\x1b[0m`;
|
|
171
161
|
}
|
|
172
|
-
return line.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\b
|
|
162
|
+
return line.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\b[a-zA-Z_$][\w$]*\b)|(\/\/[^\n]*|#[^\n]*)/g, (m, str, ident, comment) => {
|
|
173
163
|
if (str)
|
|
174
164
|
return `\x1b[32m${str}\x1b[0m`;
|
|
175
|
-
if (num)
|
|
176
|
-
return `\x1b[33m${num}\x1b[0m`;
|
|
177
165
|
if (comment)
|
|
178
|
-
return `\x1b[
|
|
166
|
+
return `\x1b[90m${comment}\x1b[0m`;
|
|
179
167
|
if (ident && KEYWORDS.has(ident))
|
|
180
168
|
return `\x1b[1;36m${ident}\x1b[0m`;
|
|
181
169
|
return m;
|
|
@@ -186,7 +174,7 @@ function formatInlineMarkdown(text) {
|
|
|
186
174
|
// Links MUST be converted first: the other rules inject ANSI escape codes,
|
|
187
175
|
// and the link pattern `\[[^\]]+\]\([^)]+\)` would otherwise match the `[`
|
|
188
176
|
// inside an already-inserted escape (e.g. `\x1b[1m`) and corrupt the output.
|
|
189
|
-
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "\x1b[4;36m$1\x1b[0m \x1b[
|
|
177
|
+
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "\x1b[4;36m$1\x1b[0m \x1b[90m($2)\x1b[0m");
|
|
190
178
|
line = line.replace(/\*\*\*([^*]+)\*\*\*/g, "\x1b[1;3m$1\x1b[0m");
|
|
191
179
|
line = line.replace(/\*\*([^*]+)\*\*/g, "\x1b[1m$1\x1b[0m");
|
|
192
180
|
line = line.replace(/(^|[^\w*])\*([^*\n]+)\*(?=$|[^\w*])/g, "$1\x1b[3m$2\x1b[0m");
|
|
@@ -313,7 +301,7 @@ export function markdownToAnsi(text) {
|
|
|
313
301
|
}
|
|
314
302
|
const h2 = line.match(/^##\s+(.+)$/);
|
|
315
303
|
if (h2) {
|
|
316
|
-
out.push(`\x1b[1;
|
|
304
|
+
out.push(`\x1b[1;36m${formatInlineMarkdown(h2[1])}\x1b[0m`);
|
|
317
305
|
continue;
|
|
318
306
|
}
|
|
319
307
|
const h3 = line.match(/^###\s+(.+)$/);
|
|
@@ -371,7 +359,7 @@ export function aiMarkdown(text) {
|
|
|
371
359
|
export function mutedMarkdown(text) {
|
|
372
360
|
return markdownToAnsi(text)
|
|
373
361
|
.split("\n")
|
|
374
|
-
.map((l) => `\x1b[
|
|
362
|
+
.map((l) => `\x1b[90m${l}\x1b[0m`)
|
|
375
363
|
.join("\n");
|
|
376
364
|
}
|
|
377
365
|
// ---------------------------------------------------------------------------
|
|
@@ -466,7 +454,7 @@ export function formatDuration(seconds) {
|
|
|
466
454
|
return `${mins}m ${remSecs}s`;
|
|
467
455
|
}
|
|
468
456
|
export function tickDuration(seconds) {
|
|
469
|
-
return `\x1b[22;37m${formatDuration(seconds)}\x1b[
|
|
457
|
+
return `\x1b[22;37m${formatDuration(seconds)}\x1b[90m`;
|
|
470
458
|
}
|
|
471
459
|
export function printAiChunk(chunk) {
|
|
472
460
|
if (!chunk)
|
|
@@ -566,7 +554,7 @@ export class Spinner {
|
|
|
566
554
|
draw() {
|
|
567
555
|
const text = this.render ? this.render() : "";
|
|
568
556
|
const icon = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
|
|
569
|
-
const rendered = `\x1b[36m${icon}\x1b[0m \x1b[
|
|
557
|
+
const rendered = `\x1b[36m${icon}\x1b[0m \x1b[90m${text}\x1b[0m`;
|
|
570
558
|
// Nothing changed on this tick -> do not rewrite the line (avoids flicker).
|
|
571
559
|
if (rendered === this.lastRendered)
|
|
572
560
|
return;
|
|
@@ -807,7 +795,7 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
807
795
|
runs.push({ text: " ", style: "" });
|
|
808
796
|
if (!buffer) {
|
|
809
797
|
runs.push({ text: PROMPT_CARET, style: "\x1b[1;36m" });
|
|
810
|
-
runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[
|
|
798
|
+
runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[90m" });
|
|
811
799
|
}
|
|
812
800
|
else {
|
|
813
801
|
const segs = splitBlocks(buffer, pasteSpans);
|
|
@@ -903,208 +891,23 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
903
891
|
return { frame, cursorRow, cursorCol, totalRows };
|
|
904
892
|
}
|
|
905
893
|
/**
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* application: ESC O M
|
|
894
|
+
* Returns true when a keypress encodes Enter in any form: a plain CR
|
|
895
|
+
* (readline name "return"), the application-keypad form (ESC O M), or a
|
|
896
|
+
* modifier-prefixed CSI sequence (CSI-u ESC [ 13 ; N u, kitty ESC [ 27 ; N ;
|
|
897
|
+
* 13 ~). The prompt is single-line, so every Enter submits — there is no
|
|
898
|
+
* Shift+Enter newline mode, and a bare LF (Ctrl+J, name "enter"/"linefeed")
|
|
899
|
+
* is not a real Enter.
|
|
913
900
|
*/
|
|
914
|
-
|
|
915
|
-
* Parses a complete (reassembled) escape-sequence encoding of Enter and
|
|
916
|
-
* classifies it as a newline (Shift+Enter) or a submit (plain Enter).
|
|
917
|
-
*
|
|
918
|
-
* Return values:
|
|
919
|
-
* "newline" — Shift+Enter (insert a new row)
|
|
920
|
-
* "submit" — plain Enter (submit the prompt)
|
|
921
|
-
* null — not an Enter sequence
|
|
922
|
-
*
|
|
923
|
-
* Handles every encoding a terminal can produce:
|
|
924
|
-
* CSI-u: ESC [ 13 ; M u (M = xterm bitmask + 1)
|
|
925
|
-
* legacy / kitty: ESC [ 13 ; M ~ / ESC [ 13 ; M _
|
|
926
|
-
* modifyOtherKeys: ESC [ 27 ; M ; 13 ~
|
|
927
|
-
* application (SS3): ESC O M
|
|
928
|
-
* win32-input-mode: ESC [ G ; R ; C ; VK ; MOD u
|
|
929
|
-
* (VK = 13 for Enter, MOD = raw bitmask where
|
|
930
|
-
* bit0 = Shift)
|
|
931
|
-
*/
|
|
932
|
-
function parseEnterSequence(seq) {
|
|
933
|
-
if (!seq)
|
|
934
|
-
return null;
|
|
935
|
-
// win32-input-mode: ESC [ guard ; row ; col ; vk ; mod u
|
|
936
|
-
let m = seq.match(/^\x1b\[(\d+);(\d+);(\d+);(\d+);(\d+)u$/);
|
|
937
|
-
if (m) {
|
|
938
|
-
const vk = parseInt(m[4], 10);
|
|
939
|
-
if (vk !== 13)
|
|
940
|
-
return null;
|
|
941
|
-
const mod = parseInt(m[5], 10);
|
|
942
|
-
return mod & 1 ? "newline" : "submit";
|
|
943
|
-
}
|
|
944
|
-
// modifyOtherKeys / kitty: ESC [ 27 ; M ; 13 ~ (or _)
|
|
945
|
-
m = seq.match(/^\x1b\[27;(\d+);13[~_]$/);
|
|
946
|
-
if (m) {
|
|
947
|
-
const mod = parseInt(m[1], 10);
|
|
948
|
-
if (mod > 0)
|
|
949
|
-
return (mod - 1) & 1 ? "newline" : "submit";
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
// CSI-u / legacy: ESC [ 13 ; M u (or ~ or _)
|
|
953
|
-
m = seq.match(/^\x1b\[13;(\d+)[~u_]$/);
|
|
954
|
-
if (m) {
|
|
955
|
-
const mod = parseInt(m[1], 10);
|
|
956
|
-
if (mod > 0)
|
|
957
|
-
return (mod - 1) & 1 ? "newline" : "submit";
|
|
958
|
-
return null;
|
|
959
|
-
}
|
|
960
|
-
// Application keypad Enter (SS3)
|
|
961
|
-
if (seq === "\x1bOM")
|
|
962
|
-
return "newline";
|
|
963
|
-
return null;
|
|
964
|
-
}
|
|
965
|
-
export function isShiftEnterKey(str, key) {
|
|
966
|
-
if (!key)
|
|
967
|
-
return false;
|
|
968
|
-
const seq = key.sequence || str || "";
|
|
969
|
-
if (parseEnterSequence(seq) === "newline")
|
|
970
|
-
return true;
|
|
971
|
-
// Shift modifier reported by readline on a return/enter/linefeed key.
|
|
972
|
-
if (key.shift &&
|
|
973
|
-
(key.name === "return" ||
|
|
974
|
-
key.name === "enter" ||
|
|
975
|
-
key.name === "linefeed" ||
|
|
976
|
-
seq === "\r" ||
|
|
977
|
-
seq === "\n")) {
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
return false;
|
|
981
|
-
}
|
|
982
|
-
export const isNewlineKey = isShiftEnterKey;
|
|
983
|
-
export function isSubmitEnterKey(str, key) {
|
|
901
|
+
function isEnterKey(str, key) {
|
|
984
902
|
if (!key)
|
|
985
903
|
return false;
|
|
986
|
-
if (isShiftEnterKey(str, key))
|
|
987
|
-
return false;
|
|
988
904
|
const seq = key.sequence || str || "";
|
|
989
|
-
if (
|
|
990
|
-
return true;
|
|
991
|
-
if ((key.name === "return" ||
|
|
992
|
-
seq === "\r" ||
|
|
993
|
-
seq === "\x1b[13u" ||
|
|
994
|
-
seq === "\x1b[13;1u") &&
|
|
995
|
-
!key.shift) {
|
|
905
|
+
if (key.name === "return" || seq === "\r")
|
|
996
906
|
return true;
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
* Maps a Windows virtual-key code + shift state to a printable character, or
|
|
1002
|
-
* null if the code isn't a printable character. US-layout approximation (the
|
|
1003
|
-
* common case); enough for a text prompt.
|
|
1004
|
-
*/
|
|
1005
|
-
function vkToChar(vk, shift) {
|
|
1006
|
-
if (vk >= 48 && vk <= 57) {
|
|
1007
|
-
const symbols = [")", "!", "@", "#", "$", "%", "^", "&", "*", "("];
|
|
1008
|
-
return shift ? symbols[vk - 48] : String(vk - 48);
|
|
1009
|
-
}
|
|
1010
|
-
if (vk >= 65 && vk <= 90) {
|
|
1011
|
-
return shift ? String.fromCharCode(vk) : String.fromCharCode(vk).toLowerCase();
|
|
1012
|
-
}
|
|
1013
|
-
if (vk === 186)
|
|
1014
|
-
return shift ? ":" : ";";
|
|
1015
|
-
if (vk === 187)
|
|
1016
|
-
return shift ? "+" : "=";
|
|
1017
|
-
if (vk === 188)
|
|
1018
|
-
return shift ? "<" : ",";
|
|
1019
|
-
if (vk === 189)
|
|
1020
|
-
return shift ? "_" : "-";
|
|
1021
|
-
if (vk === 190)
|
|
1022
|
-
return shift ? ">" : ".";
|
|
1023
|
-
if (vk === 191)
|
|
1024
|
-
return shift ? "?" : "/";
|
|
1025
|
-
if (vk === 192)
|
|
1026
|
-
return shift ? "~" : "`";
|
|
1027
|
-
if (vk === 219)
|
|
1028
|
-
return shift ? "{" : "[";
|
|
1029
|
-
if (vk === 220)
|
|
1030
|
-
return shift ? "|" : "\\";
|
|
1031
|
-
if (vk === 221)
|
|
1032
|
-
return shift ? "}" : "]";
|
|
1033
|
-
if (vk === 222)
|
|
1034
|
-
return shift ? '"' : "'";
|
|
1035
|
-
return null;
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Decodes a complete win32-input-mode sequence (ESC [ G ; R ; C ; VK ; MOD u)
|
|
1039
|
-
* into a key action. Returns a descriptor for the prompt to execute, or null
|
|
1040
|
-
* if the sequence isn't a win32-input-mode sequence.
|
|
1041
|
-
*/
|
|
1042
|
-
function decodeWin32Key(seq) {
|
|
1043
|
-
const m = seq.match(/^\x1b\[(\d+);(\d+);(\d+);(\d+);(\d+)u$/);
|
|
1044
|
-
if (!m)
|
|
1045
|
-
return null;
|
|
1046
|
-
const vk = parseInt(m[4], 10);
|
|
1047
|
-
const mod = parseInt(m[5], 10);
|
|
1048
|
-
const shift = (mod & 1) === 1;
|
|
1049
|
-
const ctrl = (mod & 4) === 4;
|
|
1050
|
-
const alt = (mod & 2) === 2;
|
|
1051
|
-
const cmd = (c) => ({ type: "cmd", cmd: c });
|
|
1052
|
-
const text = (t) => ({ type: "text", text: t });
|
|
1053
|
-
// Control combos
|
|
1054
|
-
if (ctrl && vk === 67)
|
|
1055
|
-
return cmd("interrupt"); // Ctrl+C
|
|
1056
|
-
if (ctrl && vk === 68)
|
|
1057
|
-
return cmd("eof"); // Ctrl+D
|
|
1058
|
-
if (ctrl && vk === 90)
|
|
1059
|
-
return cmd("eof"); // Ctrl+Z
|
|
1060
|
-
if (ctrl && vk === 65)
|
|
1061
|
-
return cmd("home"); // Ctrl+A
|
|
1062
|
-
if (ctrl && vk === 69)
|
|
1063
|
-
return cmd("end"); // Ctrl+E
|
|
1064
|
-
if (ctrl && vk === 85)
|
|
1065
|
-
return cmd("clear-line"); // Ctrl+U
|
|
1066
|
-
if (ctrl && vk === 75)
|
|
1067
|
-
return cmd("kill-line"); // Ctrl+K
|
|
1068
|
-
if (ctrl && vk === 87)
|
|
1069
|
-
return cmd("delete-word"); // Ctrl+W
|
|
1070
|
-
switch (vk) {
|
|
1071
|
-
case 13: // Enter
|
|
1072
|
-
return shift ? cmd("newline") : cmd("submit");
|
|
1073
|
-
case 8: // Backspace
|
|
1074
|
-
return cmd(ctrl ? "delete-word" : "backspace");
|
|
1075
|
-
case 46: // Delete
|
|
1076
|
-
return cmd("delete");
|
|
1077
|
-
case 37: // Left
|
|
1078
|
-
return ctrl ? cmd("back-word") : cmd("left");
|
|
1079
|
-
case 39: // Right
|
|
1080
|
-
return ctrl ? cmd("forward-word") : cmd("right");
|
|
1081
|
-
case 38: // Up
|
|
1082
|
-
return cmd("up");
|
|
1083
|
-
case 40: // Down
|
|
1084
|
-
return cmd("down");
|
|
1085
|
-
case 36: // Home
|
|
1086
|
-
return cmd("home");
|
|
1087
|
-
case 35: // End
|
|
1088
|
-
return cmd("end");
|
|
1089
|
-
case 33: // PageUp
|
|
1090
|
-
return cmd("up");
|
|
1091
|
-
case 34: // PageDown
|
|
1092
|
-
return cmd("down");
|
|
1093
|
-
case 9: // Tab
|
|
1094
|
-
return cmd("tab");
|
|
1095
|
-
case 32: // Space
|
|
1096
|
-
return text(" ");
|
|
1097
|
-
case 27: // Escape
|
|
1098
|
-
return cmd("escape");
|
|
1099
|
-
default: {
|
|
1100
|
-
if (alt)
|
|
1101
|
-
return null; // Alt combos unsupported -> ignore
|
|
1102
|
-
const ch = vkToChar(vk, shift);
|
|
1103
|
-
if (ch)
|
|
1104
|
-
return text(ch);
|
|
1105
|
-
return cmd("ignore");
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
907
|
+
return (/^\x1b\[13(;[\d;]*)?[u~_]$/.test(seq) || // CSI-u / legacy, any modifier
|
|
908
|
+
/^\x1b\[27;\d+;13[~_]$/.test(seq) || // modifyOtherKeys / kitty
|
|
909
|
+
seq === "\x1bOM" // application keypad
|
|
910
|
+
);
|
|
1108
911
|
}
|
|
1109
912
|
export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
1110
913
|
return new Promise((resolve, reject) => {
|
|
@@ -1132,19 +935,11 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
1132
935
|
let bracketedPasteBuffer = "";
|
|
1133
936
|
let pasteBurst = false;
|
|
1134
937
|
let pasteBurstTimer = null;
|
|
1135
|
-
// Accumulator for modifier-prefixed CSI sequences (e.g. Shift+Enter sent as
|
|
1136
|
-
// `\x1b[27;2;13~`), which Node's readline splits into several keypress
|
|
1137
|
-
// events instead of one, so they must be reassembled here.
|
|
1138
|
-
let modSeq = "";
|
|
1139
|
-
let modSeqTimeout = null;
|
|
1140
938
|
readline.emitKeypressEvents(process.stdin);
|
|
1141
939
|
if (process.stdin.isTTY) {
|
|
1142
940
|
process.stdin.setRawMode(true);
|
|
941
|
+
// Bracketed paste so multi-line pastes arrive inside \x1b[200~…\x1b[201~.
|
|
1143
942
|
process.stdout.write("\x1b[?2004h");
|
|
1144
|
-
// Enable win32-input-mode (preferred by Windows Terminal) and CSI-u
|
|
1145
|
-
// disambiguation so Shift+Enter arrives as a distinct code instead of a
|
|
1146
|
-
// plain Enter. Harmless if a terminal doesn't support them.
|
|
1147
|
-
process.stdout.write("\x1b[?9001h\x1b[>1u");
|
|
1148
943
|
}
|
|
1149
944
|
process.stdin.resume();
|
|
1150
945
|
hideCursor();
|
|
@@ -1167,12 +962,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
1167
962
|
if (done)
|
|
1168
963
|
return;
|
|
1169
964
|
done = true;
|
|
1170
|
-
process.stdout.write("\x1b[?2004l
|
|
965
|
+
process.stdout.write("\x1b[?2004l");
|
|
1171
966
|
process.stdin.setRawMode(false);
|
|
1172
967
|
if (pasteBurstTimer)
|
|
1173
968
|
clearTimeout(pasteBurstTimer);
|
|
1174
|
-
if (modSeqTimeout)
|
|
1175
|
-
clearTimeout(modSeqTimeout);
|
|
1176
969
|
clearBox();
|
|
1177
970
|
process.stdin.removeListener("keypress", onKeypress);
|
|
1178
971
|
process.stdin.pause();
|
|
@@ -1490,131 +1283,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
1490
1283
|
pasteBurstTimer = null;
|
|
1491
1284
|
}, 500);
|
|
1492
1285
|
};
|
|
1493
|
-
const handleRawSequence = (full) => {
|
|
1494
|
-
// Enter sequences (CSI-u, kitty, legacy, SS3, win32).
|
|
1495
|
-
const enter = parseEnterSequence(full);
|
|
1496
|
-
if (enter === "newline") {
|
|
1497
|
-
insert("\n");
|
|
1498
|
-
repaint();
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
if (enter === "submit") {
|
|
1502
|
-
if (buffer.trim())
|
|
1503
|
-
settle([buffer, pasteSpans], false);
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
|
-
// Other win32-input-mode keys (arrows, backspace, etc.).
|
|
1507
|
-
const dec = decodeWin32Key(full);
|
|
1508
|
-
if (!dec)
|
|
1509
|
-
return;
|
|
1510
|
-
if (dec.type === "text") {
|
|
1511
|
-
insert(dec.text);
|
|
1512
|
-
repaint();
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
switch (dec.cmd) {
|
|
1516
|
-
case "interrupt":
|
|
1517
|
-
settle(new Error("interrupt"), true);
|
|
1518
|
-
return;
|
|
1519
|
-
case "eof":
|
|
1520
|
-
settle(new Error("eof"), true);
|
|
1521
|
-
return;
|
|
1522
|
-
case "backspace":
|
|
1523
|
-
backspace();
|
|
1524
|
-
repaint();
|
|
1525
|
-
return;
|
|
1526
|
-
case "delete-word":
|
|
1527
|
-
deleteWordBefore();
|
|
1528
|
-
repaint();
|
|
1529
|
-
return;
|
|
1530
|
-
case "delete":
|
|
1531
|
-
deleteChar();
|
|
1532
|
-
repaint();
|
|
1533
|
-
return;
|
|
1534
|
-
case "left":
|
|
1535
|
-
moveLeft();
|
|
1536
|
-
repaint();
|
|
1537
|
-
return;
|
|
1538
|
-
case "right":
|
|
1539
|
-
moveRight();
|
|
1540
|
-
repaint();
|
|
1541
|
-
return;
|
|
1542
|
-
case "back-word": {
|
|
1543
|
-
const sp = buffer.slice(0, cursor).lastIndexOf(" ");
|
|
1544
|
-
cursor = sp === -1 ? 0 : sp + 1;
|
|
1545
|
-
repaint();
|
|
1546
|
-
return;
|
|
1547
|
-
}
|
|
1548
|
-
case "forward-word": {
|
|
1549
|
-
const sp = buffer.indexOf(" ", cursor);
|
|
1550
|
-
cursor = sp === -1 ? buffer.length : sp + 1;
|
|
1551
|
-
repaint();
|
|
1552
|
-
return;
|
|
1553
|
-
}
|
|
1554
|
-
case "up":
|
|
1555
|
-
moveUp();
|
|
1556
|
-
repaint();
|
|
1557
|
-
return;
|
|
1558
|
-
case "down":
|
|
1559
|
-
moveDown();
|
|
1560
|
-
repaint();
|
|
1561
|
-
return;
|
|
1562
|
-
case "home":
|
|
1563
|
-
cursor = 0;
|
|
1564
|
-
repaint();
|
|
1565
|
-
return;
|
|
1566
|
-
case "end":
|
|
1567
|
-
cursor = buffer.length;
|
|
1568
|
-
repaint();
|
|
1569
|
-
return;
|
|
1570
|
-
case "tab":
|
|
1571
|
-
insert(" ");
|
|
1572
|
-
repaint();
|
|
1573
|
-
return;
|
|
1574
|
-
case "clear-line":
|
|
1575
|
-
buffer = "";
|
|
1576
|
-
cursor = 0;
|
|
1577
|
-
pasteSpans = [];
|
|
1578
|
-
repaint();
|
|
1579
|
-
return;
|
|
1580
|
-
case "kill-line":
|
|
1581
|
-
buffer = buffer.slice(0, cursor);
|
|
1582
|
-
repaint();
|
|
1583
|
-
return;
|
|
1584
|
-
default:
|
|
1585
|
-
return;
|
|
1586
|
-
}
|
|
1587
|
-
};
|
|
1588
1286
|
const onKeypress = (str, key) => {
|
|
1589
|
-
// Reassemble modifier-prefixed / win32-input-mode CSI sequences, which
|
|
1590
|
-
// Node's readline splits into several keypress events (e.g. Shift+Enter
|
|
1591
|
-
// arriving as `\x1b[27;2;` + "1" + "3" + "~", or a win32-input-mode
|
|
1592
|
-
// sequence `\x1b[0;1;1;13;2u` arriving piecemeal). A "partial CSI" is
|
|
1593
|
-
// any ESC [ sequence readline left unterminated; we keep appending until
|
|
1594
|
-
// it ends in a terminator, then classify it.
|
|
1595
|
-
const seq = key?.sequence || str || "";
|
|
1596
|
-
if (modSeq) {
|
|
1597
|
-
modSeq += str || seq || "";
|
|
1598
|
-
if (/^\x1b\[[\d;]*[a-zA-Z~_u]$/.test(modSeq)) {
|
|
1599
|
-
const full = modSeq;
|
|
1600
|
-
modSeq = "";
|
|
1601
|
-
if (modSeqTimeout)
|
|
1602
|
-
clearTimeout(modSeqTimeout);
|
|
1603
|
-
modSeqTimeout = null;
|
|
1604
|
-
handleRawSequence(full);
|
|
1605
|
-
}
|
|
1606
|
-
return;
|
|
1607
|
-
}
|
|
1608
|
-
if (/^\x1b\[[\d;]*$/.test(seq)) {
|
|
1609
|
-
modSeq = seq;
|
|
1610
|
-
if (modSeqTimeout)
|
|
1611
|
-
clearTimeout(modSeqTimeout);
|
|
1612
|
-
modSeqTimeout = setTimeout(() => {
|
|
1613
|
-
modSeq = "";
|
|
1614
|
-
modSeqTimeout = null;
|
|
1615
|
-
}, 200);
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
1287
|
if (consumeBracketedPaste(str, key))
|
|
1619
1288
|
return;
|
|
1620
1289
|
if (pasteBurst && str && str.length === 1 && !key?.ctrl && !key?.meta) {
|
|
@@ -1658,12 +1327,12 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
1658
1327
|
repaint();
|
|
1659
1328
|
return;
|
|
1660
1329
|
}
|
|
1661
|
-
if (
|
|
1330
|
+
if (pasteBurst && isEnterKey(str, key)) {
|
|
1662
1331
|
insert("\n");
|
|
1663
1332
|
repaint();
|
|
1664
1333
|
return;
|
|
1665
1334
|
}
|
|
1666
|
-
if (
|
|
1335
|
+
if (isEnterKey(str, key)) {
|
|
1667
1336
|
if (buffer.trim()) {
|
|
1668
1337
|
settle([buffer, pasteSpans], false);
|
|
1669
1338
|
}
|
|
@@ -1720,10 +1389,9 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
1720
1389
|
return;
|
|
1721
1390
|
}
|
|
1722
1391
|
// Ctrl+J sends a bare LF (0x0a). Node reports it as name "enter" or
|
|
1723
|
-
// "linefeed"
|
|
1724
|
-
//
|
|
1725
|
-
|
|
1726
|
-
if (key && !key.shift && (key.name === "enter" || key.name === "linefeed")) {
|
|
1392
|
+
// "linefeed". It is not a real Enter (a real Enter arrives as CR / name
|
|
1393
|
+
// "return"), so ignore it rather than submitting or inserting a newline.
|
|
1394
|
+
if (key && (key.name === "enter" || key.name === "linefeed")) {
|
|
1727
1395
|
return;
|
|
1728
1396
|
}
|
|
1729
1397
|
if (str) {
|