@oxecli/oxe 1.0.69 → 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 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[2m${desc}\x1b[0m`],
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[2m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
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[2m… ${skipped} earlier message(s) omitted …\x1b[0m\n\n`);
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[2m ${updated}\x1b[0m ${label}\x1b[2m ${items.length} items · ${toks} tok\x1b[0m`;
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[2m"} ${updated}\x1b[0m` +
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[2m"} ${items.length} items · ${toks} tok\x1b[0m`;
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[2m▲ ${lo} earlier · ▼ ${below} more\x1b[0m`;
286
+ footer = `\x1b[90m▲ ${lo} earlier · ▼ ${below} more\x1b[0m`;
287
287
  else if (lo > 0)
288
- footer = `\x1b[2m▲ ${lo} earlier\x1b[0m`;
288
+ footer = `\x1b[90m▲ ${lo} earlier\x1b[0m`;
289
289
  else if (below > 0)
290
- footer = `\x1b[2m▼ ${below} more\x1b[0m`;
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[2mResumed:\x1b[0m \x1b[1m${truncateLabel(rec["label"])}\x1b[0m\n`);
376
- process.stdout.write(`\x1b[2m(${this.inputItems.length} items · ${estimateTokens(this.inputItems).toLocaleString()} tokens)\x1b[0m\n\n`);
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[2mReasoning effort set to \x1b[1;36m${effort}\x1b[0m\x1b[2m.\x1b[0m\n`);
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[2mInterrupted agent. Press Ctrl+C again to exit, or type a new prompt.\x1b[0m\n");
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[2m tokens`);
454
+ parts.push(`Used \x1b[22;37m${stats["tokens"].toLocaleString()}\x1b[90m tokens`);
455
455
  }
456
- return `\x1b[2m(${parts.join(" · ")})\x1b[0m`;
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[2m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
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[2m ${body}\x1b[0m`;
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[2m ${body}\x1b[0m`;
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[>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[2m",
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[2;37m${line}\x1b[0m`;
160
+ return `\x1b[90m${line}\x1b[0m`;
171
161
  }
172
- return line.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\b\d+\.?\d*\b)|(\b[a-zA-Z_$][\w$]*\b)|(\/\/[^\n]*|#[^\n]*)/g, (m, str, num, ident, comment) => {
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[2;37m${comment}\x1b[0m`;
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[2m($2)\x1b[0m");
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;34m${formatInlineMarkdown(h2[1])}\x1b[0m`);
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[2m${l}\x1b[0m`)
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[2m`;
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[2m${text}\x1b[0m`;
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[2;37m" });
798
+ runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[90m" });
811
799
  }
812
800
  else {
813
801
  const segs = splitBlocks(buffer, pasteSpans);
@@ -903,209 +891,23 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
903
891
  return { frame, cursorRow, cursorCol, totalRows };
904
892
  }
905
893
  /**
906
- * Generic CSI Shift+Enter detection. The modifier encoding used by CSI-u,
907
- * legacy and kitty/modifyOtherKeys is a bitmask + 1, where Shift is bit0
908
- * (so modifier 2 = shift, 4 = shift+alt, 6 = shift+ctrl, ...). Enter has
909
- * key code 13. Handles:
910
- * CSI-u / legacy: ESC [ 13 ; M u and ESC [ 13 ; M ~
911
- * modifyOtherKeys: ESC [ 27 ; M ; 13 ~
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
- * bit2 = 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 & 4 ? "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 (parseEnterSequence(seq) === "submit")
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
- return false;
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
- // win32-input-mode MOD bitmask: bit2 = Shift, bit1 = Ctrl, bit0 = Alt
1049
- const shift = (mod & 4) === 4;
1050
- const ctrl = (mod & 2) === 2;
1051
- const alt = (mod & 1) === 1;
1052
- const cmd = (c) => ({ type: "cmd", cmd: c });
1053
- const text = (t) => ({ type: "text", text: t });
1054
- // Control combos
1055
- if (ctrl && vk === 67)
1056
- return cmd("interrupt"); // Ctrl+C
1057
- if (ctrl && vk === 68)
1058
- return cmd("eof"); // Ctrl+D
1059
- if (ctrl && vk === 90)
1060
- return cmd("eof"); // Ctrl+Z
1061
- if (ctrl && vk === 65)
1062
- return cmd("home"); // Ctrl+A
1063
- if (ctrl && vk === 69)
1064
- return cmd("end"); // Ctrl+E
1065
- if (ctrl && vk === 85)
1066
- return cmd("clear-line"); // Ctrl+U
1067
- if (ctrl && vk === 75)
1068
- return cmd("kill-line"); // Ctrl+K
1069
- if (ctrl && vk === 87)
1070
- return cmd("delete-word"); // Ctrl+W
1071
- switch (vk) {
1072
- case 13: // Enter
1073
- return shift ? cmd("newline") : cmd("submit");
1074
- case 8: // Backspace
1075
- return cmd(ctrl ? "delete-word" : "backspace");
1076
- case 46: // Delete
1077
- return cmd("delete");
1078
- case 37: // Left
1079
- return ctrl ? cmd("back-word") : cmd("left");
1080
- case 39: // Right
1081
- return ctrl ? cmd("forward-word") : cmd("right");
1082
- case 38: // Up
1083
- return cmd("up");
1084
- case 40: // Down
1085
- return cmd("down");
1086
- case 36: // Home
1087
- return cmd("home");
1088
- case 35: // End
1089
- return cmd("end");
1090
- case 33: // PageUp
1091
- return cmd("up");
1092
- case 34: // PageDown
1093
- return cmd("down");
1094
- case 9: // Tab
1095
- return cmd("tab");
1096
- case 32: // Space
1097
- return text(" ");
1098
- case 27: // Escape
1099
- return cmd("escape");
1100
- default: {
1101
- if (alt)
1102
- return null; // Alt combos unsupported -> ignore
1103
- const ch = vkToChar(vk, shift);
1104
- if (ch)
1105
- return text(ch);
1106
- return cmd("ignore");
1107
- }
1108
- }
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
+ );
1109
911
  }
1110
912
  export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1111
913
  return new Promise((resolve, reject) => {
@@ -1133,23 +935,11 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1133
935
  let bracketedPasteBuffer = "";
1134
936
  let pasteBurst = false;
1135
937
  let pasteBurstTimer = null;
1136
- // Accumulator for modifier-prefixed CSI sequences (e.g. Shift+Enter sent as
1137
- // `\x1b[27;2;13~`), which Node's readline splits into several keypress
1138
- // events instead of one, so they must be reassembled here.
1139
- let modSeq = "";
1140
- let modSeqTimeout = null;
1141
938
  readline.emitKeypressEvents(process.stdin);
1142
939
  if (process.stdin.isTTY) {
1143
940
  process.stdin.setRawMode(true);
941
+ // Bracketed paste so multi-line pastes arrive inside \x1b[200~…\x1b[201~.
1144
942
  process.stdout.write("\x1b[?2004h");
1145
- // Enable CSI-u disambiguation so Shift+Enter arrives as a distinct code
1146
- // (`\x1b[13;2u`) instead of a plain Enter. Plain keys still arrive as
1147
- // single keypress events. Deliberately NOT enabling win32-input-mode
1148
- // (`\x1b[?9001h`): it rewrites EVERY keystroke into a long CSI sequence
1149
- // that readline splits across many events, and an interrupted split
1150
- // leaves modSeq stuck so the next keystroke is silently dropped —
1151
- // that regression made typing in the prompt impossible.
1152
- process.stdout.write("\x1b[>1u");
1153
943
  }
1154
944
  process.stdin.resume();
1155
945
  hideCursor();
@@ -1176,8 +966,6 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1176
966
  process.stdin.setRawMode(false);
1177
967
  if (pasteBurstTimer)
1178
968
  clearTimeout(pasteBurstTimer);
1179
- if (modSeqTimeout)
1180
- clearTimeout(modSeqTimeout);
1181
969
  clearBox();
1182
970
  process.stdin.removeListener("keypress", onKeypress);
1183
971
  process.stdin.pause();
@@ -1495,140 +1283,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1495
1283
  pasteBurstTimer = null;
1496
1284
  }, 500);
1497
1285
  };
1498
- const handleRawSequence = (full) => {
1499
- // Enter sequences (CSI-u, kitty, legacy, SS3, win32).
1500
- const enter = parseEnterSequence(full);
1501
- if (enter === "newline") {
1502
- insert("\n");
1503
- repaint();
1504
- return;
1505
- }
1506
- if (enter === "submit") {
1507
- if (buffer.trim())
1508
- settle([buffer, pasteSpans], false);
1509
- return;
1510
- }
1511
- // Other win32-input-mode keys (arrows, backspace, etc.).
1512
- const dec = decodeWin32Key(full);
1513
- if (!dec)
1514
- return;
1515
- if (dec.type === "text") {
1516
- insert(dec.text);
1517
- repaint();
1518
- return;
1519
- }
1520
- switch (dec.cmd) {
1521
- case "interrupt":
1522
- settle(new Error("interrupt"), true);
1523
- return;
1524
- case "eof":
1525
- settle(new Error("eof"), true);
1526
- return;
1527
- case "backspace":
1528
- backspace();
1529
- repaint();
1530
- return;
1531
- case "delete-word":
1532
- deleteWordBefore();
1533
- repaint();
1534
- return;
1535
- case "delete":
1536
- deleteChar();
1537
- repaint();
1538
- return;
1539
- case "left":
1540
- moveLeft();
1541
- repaint();
1542
- return;
1543
- case "right":
1544
- moveRight();
1545
- repaint();
1546
- return;
1547
- case "back-word": {
1548
- const sp = buffer.slice(0, cursor).lastIndexOf(" ");
1549
- cursor = sp === -1 ? 0 : sp + 1;
1550
- repaint();
1551
- return;
1552
- }
1553
- case "forward-word": {
1554
- const sp = buffer.indexOf(" ", cursor);
1555
- cursor = sp === -1 ? buffer.length : sp + 1;
1556
- repaint();
1557
- return;
1558
- }
1559
- case "up":
1560
- moveUp();
1561
- repaint();
1562
- return;
1563
- case "down":
1564
- moveDown();
1565
- repaint();
1566
- return;
1567
- case "home":
1568
- cursor = 0;
1569
- repaint();
1570
- return;
1571
- case "end":
1572
- cursor = buffer.length;
1573
- repaint();
1574
- return;
1575
- case "tab":
1576
- insert(" ");
1577
- repaint();
1578
- return;
1579
- case "clear-line":
1580
- buffer = "";
1581
- cursor = 0;
1582
- pasteSpans = [];
1583
- repaint();
1584
- return;
1585
- case "kill-line":
1586
- buffer = buffer.slice(0, cursor);
1587
- repaint();
1588
- return;
1589
- default:
1590
- return;
1591
- }
1592
- };
1593
1286
  const onKeypress = (str, key) => {
1594
- // Reassemble modifier-prefixed CSI sequences that Node's readline may
1595
- // split across keypress events (e.g. `\x1b[13;2u` arriving as `\x1b[13;2`
1596
- // + "u"). Only start/continue accumulation on bytes that can belong to a
1597
- // CSI sequence: digits / `;` while incomplete, or a terminator byte
1598
- // [A-Z~_u] that completes it. Any other keystroke (a typed letter, for
1599
- // instance) ABANDONS the partial instead of being swallowed, so a stuck
1600
- // partial can never eat input.
1601
- const seq = key?.sequence || str || "";
1602
- if (seq.startsWith("\x1b[") && /^\x1b\[[\d;]*$/.test(seq)) {
1603
- modSeq = seq;
1604
- if (modSeqTimeout)
1605
- clearTimeout(modSeqTimeout);
1606
- modSeqTimeout = setTimeout(() => {
1607
- modSeq = "";
1608
- modSeqTimeout = null;
1609
- }, 200);
1610
- return;
1611
- }
1612
- if (modSeq) {
1613
- const s = str ?? seq ?? "";
1614
- const completes = /^\x1b\[[\d;]*[A-Z~_u]$/.test(modSeq + s);
1615
- if (/^[0-9;]$/.test(s) || completes) {
1616
- modSeq += s;
1617
- if (completes) {
1618
- const full = modSeq;
1619
- modSeq = "";
1620
- if (modSeqTimeout)
1621
- clearTimeout(modSeqTimeout);
1622
- modSeqTimeout = null;
1623
- handleRawSequence(full);
1624
- }
1625
- return;
1626
- }
1627
- modSeq = "";
1628
- if (modSeqTimeout)
1629
- clearTimeout(modSeqTimeout);
1630
- modSeqTimeout = null;
1631
- }
1632
1287
  if (consumeBracketedPaste(str, key))
1633
1288
  return;
1634
1289
  if (pasteBurst && str && str.length === 1 && !key?.ctrl && !key?.meta) {
@@ -1672,12 +1327,12 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1672
1327
  repaint();
1673
1328
  return;
1674
1329
  }
1675
- if (isNewlineKey(str, key) || (pasteBurst && isSubmitEnterKey(str, key))) {
1330
+ if (pasteBurst && isEnterKey(str, key)) {
1676
1331
  insert("\n");
1677
1332
  repaint();
1678
1333
  return;
1679
1334
  }
1680
- if (isSubmitEnterKey(str, key)) {
1335
+ if (isEnterKey(str, key)) {
1681
1336
  if (buffer.trim()) {
1682
1337
  settle([buffer, pasteSpans], false);
1683
1338
  }
@@ -1734,10 +1389,9 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1734
1389
  return;
1735
1390
  }
1736
1391
  // Ctrl+J sends a bare LF (0x0a). Node reports it as name "enter" or
1737
- // "linefeed" with no shift modifier. Only Shift+Enter should create a
1738
- // new row, so ignore a lone LF (it is not a real Enter a real Enter
1739
- // arrives as CR / name "return").
1740
- 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")) {
1741
1395
  return;
1742
1396
  }
1743
1397
  if (str) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.69",
3
+ "version": "1.0.70",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },