@oxecli/oxe 1.0.22 → 1.0.24

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
@@ -219,12 +219,13 @@ export class CLI {
219
219
  const label = truncateLabel(rec["label"]);
220
220
  const items = rec["input_items"] ?? [];
221
221
  const toks = estimateTokens(items).toLocaleString();
222
- const content = `\x1b[1m${sid.padStart(4)}\x1b[0m` +
223
- `\x1b[2m ${updated}\x1b[0m ${label}` +
224
- `\x1b[2m ${items.length} items · ${toks} tok\x1b[0m`;
222
+ const selectedRow = idx === selected;
223
+ const content = `${selectedRow ? "\x1b[1m\x1b[37m" : "\x1b[1m"}${sid.padStart(4)}\x1b[0m` +
224
+ `${selectedRow ? "\x1b[2m\x1b[36m" : "\x1b[2m"} ${updated}\x1b[0m` +
225
+ `${selectedRow ? "\x1b[37m" : ""} ${label}\x1b[0m` +
226
+ `${selectedRow ? "\x1b[2m\x1b[34m" : "\x1b[2m"} ${items.length} items · ${toks} tok\x1b[0m`;
225
227
  rows.push({
226
- cells: [idx === selected ? "\x1b[1m\x1b[36m❯\x1b[0m" : "", content],
227
- style: idx === selected ? "\x1b[1m" : undefined,
228
+ cells: [selectedRow ? "\x1b[1m\x1b[36m❯\x1b[0m" : "", content],
228
229
  });
229
230
  }
230
231
  const below = total - hi;
@@ -318,7 +319,7 @@ export class CLI {
318
319
  this.sessionId = sid;
319
320
  engine.reasoningEffort = String(rec["reasoning_effort"] ?? default_reasoning_effort);
320
321
  clearScreen();
321
- renderPanel("AI CODING AGENT", "", `Engine: Ready ${sid}`, false);
322
+ renderPanel("AI CODING AGENT", "", "Engine: Ready", false);
322
323
  process.stdout.write("\n");
323
324
  process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
324
325
  process.stdout.write("\n");
package/dist/ui.js CHANGED
@@ -728,6 +728,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
728
728
  const hist = history.slice();
729
729
  let histIdx = hist.length;
730
730
  let draft = null;
731
+ let bracketedPaste = false;
732
+ let bracketedPasteBuffer = "";
733
+ let pasteBurst = false;
734
+ let pasteBurstTimer = null;
731
735
  readline.emitKeypressEvents(process.stdin);
732
736
  if (process.stdin.isTTY) {
733
737
  process.stdin.setRawMode(true);
@@ -763,6 +767,8 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
763
767
  return;
764
768
  done = true;
765
769
  process.stdin.setRawMode(false);
770
+ if (pasteBurstTimer)
771
+ clearTimeout(pasteBurstTimer);
766
772
  clearBox();
767
773
  process.stdin.removeListener("keypress", onKeypress);
768
774
  process.stdin.pause();
@@ -863,6 +869,20 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
863
869
  draft = { buffer, cursor, spans: pasteSpans.slice() };
864
870
  histIdx = hist.length;
865
871
  }
872
+ // A collapsed paste is one editable unit. Backspace removes the entire
873
+ // pasted block, matching the Python prompt rather than deleting only its
874
+ // final character.
875
+ const containing = pasteSpans.find(([s, e]) => s < cursor && cursor <= e);
876
+ if (containing) {
877
+ const [start, end] = containing;
878
+ buffer = buffer.slice(0, start) + buffer.slice(end);
879
+ const delta = start - end;
880
+ pasteSpans = pasteSpans
881
+ .filter(([s, e]) => s !== start || e !== end)
882
+ .map(([s, e]) => (s >= end ? [s + delta, e + delta] : [s, e]));
883
+ cursor = start;
884
+ return;
885
+ }
866
886
  // remove one grapheme before cursor
867
887
  const before = Array.from(buffer.slice(0, cursor));
868
888
  before.pop();
@@ -974,6 +994,8 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
974
994
  if (!key)
975
995
  return false;
976
996
  // Bare \n / Ctrl+J = newline.
997
+ if (key.ctrl && key.name === "j")
998
+ return true;
977
999
  if (key.name === "enter")
978
1000
  return !!(key.shift || key.ctrl || key.meta);
979
1001
  // Enter with a modifier (Shift/Ctrl/Alt/Meta) = newline.
@@ -1005,7 +1027,69 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1005
1027
  return true;
1006
1028
  return false;
1007
1029
  };
1030
+ const consumeBracketedPaste = (str, key) => {
1031
+ const start = "\x1b[200~";
1032
+ const end = "\x1b[201~";
1033
+ const sequence = key?.sequence || "";
1034
+ const value = str || "";
1035
+ if (!bracketedPaste && (sequence === start || value === start)) {
1036
+ bracketedPaste = true;
1037
+ bracketedPasteBuffer = "";
1038
+ const inline = value === start ? "" : value.startsWith(start) ? value.slice(start.length) : "";
1039
+ if (inline)
1040
+ bracketedPasteBuffer = inline;
1041
+ if (inline.includes(end)) {
1042
+ const endAt = inline.indexOf(end);
1043
+ const pasted = inline.slice(0, endAt).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1044
+ bracketedPaste = false;
1045
+ bracketedPasteBuffer = "";
1046
+ if (pasted)
1047
+ insert(pasted, true);
1048
+ repaint();
1049
+ }
1050
+ return true;
1051
+ }
1052
+ if (!bracketedPaste)
1053
+ return false;
1054
+ if (sequence === end || value === end) {
1055
+ const pasted = bracketedPasteBuffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1056
+ bracketedPaste = false;
1057
+ bracketedPasteBuffer = "";
1058
+ if (pasted)
1059
+ insert(pasted, true);
1060
+ repaint();
1061
+ return true;
1062
+ }
1063
+ const chunk = value || (sequence && !sequence.startsWith("\x1b[") ? sequence : "");
1064
+ if (chunk) {
1065
+ const endAt = chunk.indexOf(end);
1066
+ if (endAt >= 0) {
1067
+ bracketedPasteBuffer += chunk.slice(0, endAt);
1068
+ const pasted = bracketedPasteBuffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1069
+ bracketedPaste = false;
1070
+ bracketedPasteBuffer = "";
1071
+ if (pasted)
1072
+ insert(pasted, true);
1073
+ repaint();
1074
+ }
1075
+ else {
1076
+ bracketedPasteBuffer += chunk;
1077
+ }
1078
+ }
1079
+ return true;
1080
+ };
1081
+ const markPasteBurst = () => {
1082
+ pasteBurst = true;
1083
+ if (pasteBurstTimer)
1084
+ clearTimeout(pasteBurstTimer);
1085
+ pasteBurstTimer = setTimeout(() => {
1086
+ pasteBurst = false;
1087
+ pasteBurstTimer = null;
1088
+ }, 90);
1089
+ };
1008
1090
  const onKeypress = (str, key) => {
1091
+ if (consumeBracketedPaste(str, key))
1092
+ return;
1009
1093
  if (key && key.ctrl && key.name === "c") {
1010
1094
  settle(new Error("interrupt"), true);
1011
1095
  return;
@@ -1014,6 +1098,15 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1014
1098
  settle(new Error("eof"), true);
1015
1099
  return;
1016
1100
  }
1101
+ // Any modified Enter (Shift/Ctrl+Enter) inserts a newline. This check
1102
+ // must happen before plain submit detection because some terminals expose
1103
+ // Shift+Enter as `return` while keeping the modifier only in the escape
1104
+ // sequence.
1105
+ if (isNewlineKey(key) || (pasteBurst && isSubmitEnterKey(key))) {
1106
+ insert("\n");
1107
+ repaint();
1108
+ return;
1109
+ }
1017
1110
  // Plain Enter submits; any modified Enter (Shift/Ctrl+Enter) inserts a
1018
1111
  // newline, mirroring the Python original's _read_input_event (which maps
1019
1112
  // \x1b[13;2u / \x1b[13;5u / Shift+\r to "\n").
@@ -1023,11 +1116,6 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1023
1116
  }
1024
1117
  return;
1025
1118
  }
1026
- if (isNewlineKey(key)) {
1027
- insert("\n");
1028
- repaint();
1029
- return;
1030
- }
1031
1119
  if (key && key.name === "backspace") {
1032
1120
  backspace();
1033
1121
  repaint();
@@ -1066,7 +1154,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1066
1154
  if (str) {
1067
1155
  // Multi-char sequence = a paste (single-char typed keys arrive one per
1068
1156
  // event). Tag it so the prompt box collapses it when it meets the rules.
1069
- insert(str, str.length > 1);
1157
+ const isPaste = str.length > 1;
1158
+ insert(str.replace(/\r\n/g, "\n").replace(/\r/g, "\n"), isPaste);
1159
+ if (isPaste)
1160
+ markPasteBurst();
1070
1161
  repaint();
1071
1162
  }
1072
1163
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },