@oxecli/oxe 1.0.56 → 1.0.58

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/engine.js CHANGED
@@ -43,7 +43,7 @@ export class InferenceEngine {
43
43
  keyData;
44
44
  client;
45
45
  workActive = false;
46
- workRows = 2;
46
+ workRows = 0;
47
47
  inQuery = false;
48
48
  activeAbort = null;
49
49
  interrupted = false;
@@ -149,7 +149,7 @@ export class InferenceEngine {
149
149
  process.stdout.write("\x1b[1A\x1b[2K");
150
150
  }
151
151
  process.stdout.write(mutedMarkdown(text) + "\n\n");
152
- this.workRows = displayRows(text) + 1;
152
+ this.workRows = displayRows(mutedMarkdown(text)) + 1;
153
153
  }
154
154
  clearWorkLine() {
155
155
  if (!this.workActive || this.workRows < 1)
@@ -196,7 +196,7 @@ export class InferenceEngine {
196
196
  else {
197
197
  process.stdout.write(mutedMarkdown(`Worked for ${t}`) + "\n\n");
198
198
  this.workActive = true;
199
- this.workRows = displayRows(`Worked for ${t}`) + 1;
199
+ this.workRows = displayRows(mutedMarkdown(`Worked for ${t}`)) + 1;
200
200
  }
201
201
  };
202
202
  const finishThinking = (report) => {
@@ -403,7 +403,7 @@ export class InferenceEngine {
403
403
  return [conversation, false];
404
404
  const started = Date.now();
405
405
  const comp = new Spinner();
406
- comp.start("Compacting conversation `0s`");
406
+ comp.start(`Compacting conversation ${tickDuration(0)}`);
407
407
  const compTimer = setInterval(() => {
408
408
  comp.update(`Compacting conversation ${tickDuration((Date.now() - started) / 1000)}`);
409
409
  }, 100);
package/dist/ui.js CHANGED
@@ -64,6 +64,38 @@ export function stripAnsi(s) {
64
64
  export function plainLen(s) {
65
65
  return stripAnsi(s).length;
66
66
  }
67
+ // Truncate styled text to a max number of visible characters while keeping
68
+ // ANSI escape sequences intact (never cut mid-sequence).
69
+ export function truncateStyled(text, maxVisible) {
70
+ if (maxVisible <= 0)
71
+ return "";
72
+ if (plainLen(text) <= maxVisible)
73
+ return text;
74
+ const re = ANSI_RE;
75
+ re.lastIndex = 0;
76
+ let out = "";
77
+ let visible = 0;
78
+ let last = 0;
79
+ let m;
80
+ while ((m = re.exec(text)) !== null) {
81
+ if (m.index > last) {
82
+ const seg = text.slice(last, m.index);
83
+ const room = maxVisible - visible;
84
+ if (room <= 0)
85
+ return out;
86
+ out += seg.slice(0, room);
87
+ visible += Math.min(seg.length, room);
88
+ if (visible >= maxVisible)
89
+ return out;
90
+ }
91
+ out += m[0];
92
+ last = m.index + m[0].length;
93
+ }
94
+ if (visible < maxVisible) {
95
+ out += text.slice(last, last + (maxVisible - visible));
96
+ }
97
+ return out;
98
+ }
67
99
  // ---------------------------------------------------------------------------
68
100
  // Markup tag -> ANSI
69
101
  // ---------------------------------------------------------------------------
@@ -334,11 +366,10 @@ export function aiMarkdown(text) {
334
366
  return markdownToAnsi(text);
335
367
  }
336
368
  export function mutedMarkdown(text) {
337
- const dimmed = text
369
+ return markdownToAnsi(text)
338
370
  .split("\n")
339
371
  .map((l) => `\x1b[2m${l}\x1b[0m`)
340
372
  .join("\n");
341
- return markdownToAnsi(dimmed);
342
373
  }
343
374
  // ---------------------------------------------------------------------------
344
375
  // Panel Rendering
@@ -377,15 +408,9 @@ function panelString(content, title = "", subtitle = "", expand = true, borderSt
377
408
  for (let i = 0; i < styledLines.length; i++) {
378
409
  const line = styledLines[i];
379
410
  const plain = plainLines[i] ?? "";
380
- if (plain.length > innerW) {
381
- // Truncate cleanly if single line overflows max box width
382
- const truncated = line.slice(0, innerW);
383
- out.push(`\x1b[${borderStyle}m│\x1b[0m ${truncated} \x1b[${borderStyle}m│\x1b[0m`);
384
- }
385
- else {
386
- const pad = Math.max(innerW - plain.length, 0);
387
- out.push(`\x1b[${borderStyle}m│\x1b[0m ${line}${" ".repeat(pad)} \x1b[${borderStyle}m│\x1b[0m`);
388
- }
411
+ const pad = Math.max(innerW - plain.length, 0);
412
+ const display = plain.length > innerW ? truncateStyled(line, innerW) : line;
413
+ out.push(`\x1b[${borderStyle}m│\x1b[0m ${display}${" ".repeat(pad)} \x1b[${borderStyle}m│\x1b[0m`);
389
414
  }
390
415
  const bottom = `╰${embed(subtitle, "center")}╯`;
391
416
  out.push(`\x1b[${borderStyle}m${bottom}\x1b[0m`);
@@ -1002,6 +1027,20 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1002
1027
  }
1003
1028
  cursor += text.length;
1004
1029
  };
1030
+ // A paste span is treated as a single atomic "block" only when it collapses
1031
+ // to a marker (large pastes). Small inline pastes behave like normal text.
1032
+ const spanCollapsed = (span) => {
1033
+ return shouldCollapsePaste(buffer.slice(span[0], span[1]));
1034
+ };
1035
+ const deleteWholeSpan = (span) => {
1036
+ const [start, end] = span;
1037
+ buffer = buffer.slice(0, start) + buffer.slice(end);
1038
+ const delta = start - end;
1039
+ pasteSpans = pasteSpans
1040
+ .filter(([s, e]) => s !== start || e !== end)
1041
+ .map(([s, e]) => (s >= end ? [s + delta, e + delta] : [s, e]));
1042
+ cursor = start;
1043
+ };
1005
1044
  const backspace = () => {
1006
1045
  if (cursor <= 0)
1007
1046
  return;
@@ -1010,14 +1049,8 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1010
1049
  histIdx = hist.length;
1011
1050
  }
1012
1051
  const containing = pasteSpans.find(([s, e]) => s < cursor && cursor <= e);
1013
- if (containing) {
1014
- const [start, end] = containing;
1015
- buffer = buffer.slice(0, start) + buffer.slice(end);
1016
- const delta = start - end;
1017
- pasteSpans = pasteSpans
1018
- .filter(([s, e]) => s !== start || e !== end)
1019
- .map(([s, e]) => (s >= end ? [s + delta, e + delta] : [s, e]));
1020
- cursor = start;
1052
+ if (containing && spanCollapsed(containing)) {
1053
+ deleteWholeSpan(containing);
1021
1054
  return;
1022
1055
  }
1023
1056
  const before = Array.from(buffer.slice(0, cursor));
@@ -1049,7 +1082,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1049
1082
  histIdx = hist.length;
1050
1083
  }
1051
1084
  const containing = pasteSpans.find(([s, e]) => s <= cursor && cursor < e);
1052
- if (containing) {
1085
+ if (containing && spanCollapsed(containing)) {
1053
1086
  const [start, end] = containing;
1054
1087
  buffer = buffer.slice(0, start) + buffer.slice(end);
1055
1088
  const delta = start - end;
@@ -1059,6 +1092,22 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1059
1092
  return;
1060
1093
  }
1061
1094
  buffer = buffer.slice(0, cursor) + buffer.slice(cursor + 1);
1095
+ const deletedAt = cursor;
1096
+ const adjusted = [];
1097
+ for (const [s, e] of pasteSpans) {
1098
+ if (deletedAt >= s && deletedAt < e) {
1099
+ const ne = e - 1;
1100
+ if (s < ne)
1101
+ adjusted.push([s, ne]);
1102
+ }
1103
+ else if (deletedAt < s) {
1104
+ adjusted.push([s - 1, e - 1]);
1105
+ }
1106
+ else {
1107
+ adjusted.push([s, e]);
1108
+ }
1109
+ }
1110
+ pasteSpans = adjusted.filter(([s, e]) => s < e);
1062
1111
  };
1063
1112
  const deleteWordBefore = () => {
1064
1113
  if (cursor <= 0)
@@ -1075,7 +1124,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1075
1124
  if (cursor <= 0)
1076
1125
  return;
1077
1126
  for (const [s, e] of pasteSpans) {
1078
- if (s < cursor && cursor <= e) {
1127
+ if (s < cursor && cursor <= e && spanCollapsed([s, e])) {
1079
1128
  cursor = s;
1080
1129
  return;
1081
1130
  }
@@ -1086,7 +1135,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1086
1135
  if (cursor >= buffer.length)
1087
1136
  return;
1088
1137
  for (const [s, e] of pasteSpans) {
1089
- if (s <= cursor && cursor < e) {
1138
+ if (s <= cursor && cursor < e && spanCollapsed([s, e])) {
1090
1139
  cursor = e;
1091
1140
  return;
1092
1141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },