@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 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[?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[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,208 +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
- * 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 (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
- 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\x1b[?9001l");
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 (isNewlineKey(str, key) || (pasteBurst && isSubmitEnterKey(str, key))) {
1330
+ if (pasteBurst && isEnterKey(str, key)) {
1662
1331
  insert("\n");
1663
1332
  repaint();
1664
1333
  return;
1665
1334
  }
1666
- if (isSubmitEnterKey(str, key)) {
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" with no shift modifier. Only Shift+Enter should create a
1724
- // new row, so ignore a lone LF (it is not a real Enter a real Enter
1725
- // arrives as CR / name "return").
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },