@oxecli/oxe 1.0.106 → 1.0.107
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 +2 -2
- package/dist/ui.js +72 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -60,7 +60,7 @@ export class CLI {
|
|
|
60
60
|
// The result body was formatted against the terminal width at tool time,
|
|
61
61
|
// but the window may be narrower on resume — re-truncate defensively so
|
|
62
62
|
// long persisted lines never run off the screen.
|
|
63
|
-
const lineMax = Math.max(terminalWidth() - 4,
|
|
63
|
+
const lineMax = Math.max(terminalWidth() - 4, 1);
|
|
64
64
|
const allLines = body.split("\n");
|
|
65
65
|
const lines = allLines
|
|
66
66
|
.slice(0, TOOL_RESULT_MAX_LINES)
|
|
@@ -81,7 +81,7 @@ export class CLI {
|
|
|
81
81
|
const truncated = truncateEllipsis(body, Math.min(max_action_chars, MAX_COMMAND_DISPLAY_CHARS), "text");
|
|
82
82
|
// truncateEllipsis appends a "…(text truncated: N chars total)" suffix, so
|
|
83
83
|
// re-truncate to the current width to keep the legacy action on one row.
|
|
84
|
-
const displayMax = Math.max(terminalWidth() - 6,
|
|
84
|
+
const displayMax = Math.max(terminalWidth() - 6, 1);
|
|
85
85
|
process.stdout.write(`${icon} ${style}${truncateStyled(truncated, displayMax)}\x1b[0m\n`);
|
|
86
86
|
}
|
|
87
87
|
printAssistantBlock(text) {
|
package/dist/ui.js
CHANGED
|
@@ -76,8 +76,11 @@ export function startDraftCapture(onInterrupt, renderBox) {
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
if (str && !key?.ctrl && !key?.meta) {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const added = str.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
80
|
+
if (draft.length + added.length <= MAX_PROMPT_CHARS) {
|
|
81
|
+
draft += added;
|
|
82
|
+
renderBox?.(draft, pasteSpans);
|
|
83
|
+
}
|
|
81
84
|
}
|
|
82
85
|
};
|
|
83
86
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -113,6 +116,21 @@ export function clearScreen() {
|
|
|
113
116
|
export function terminalWidth() {
|
|
114
117
|
return process.stdout.columns || 80;
|
|
115
118
|
}
|
|
119
|
+
/** Minimum width for boxes/cards so they don't collapse to nothing. */
|
|
120
|
+
const MIN_BOX_WIDTH = 20;
|
|
121
|
+
/** Horizontal margin reserved on each side of a box/card from the terminal edge. */
|
|
122
|
+
const BOX_MARGIN = 4;
|
|
123
|
+
/**
|
|
124
|
+
* Clamp a preferred box width so it fits inside the terminal (borders never wrap
|
|
125
|
+
* or duplicate on narrow/scaled terminals, e.g. a larger command-prompt scale)
|
|
126
|
+
* while keeping a preferred minimum. Fitting wins over the minimum when the
|
|
127
|
+
* terminal is narrower than min+margin, so cards size down smoothly instead of
|
|
128
|
+
* overflowing.
|
|
129
|
+
*/
|
|
130
|
+
function fitBoxWidth(pref, termW) {
|
|
131
|
+
const maxW = Math.max(termW - BOX_MARGIN, 1);
|
|
132
|
+
return Math.min(Math.max(Math.min(pref, maxW), MIN_BOX_WIDTH), maxW);
|
|
133
|
+
}
|
|
116
134
|
// ---------------------------------------------------------------------------
|
|
117
135
|
// ANSI and plain text length helpers
|
|
118
136
|
// ---------------------------------------------------------------------------
|
|
@@ -290,7 +308,7 @@ function formatMarkdownTable(tableLines) {
|
|
|
290
308
|
// Rich Markdown -> ANSI Terminal Renderer
|
|
291
309
|
// ---------------------------------------------------------------------------
|
|
292
310
|
export function markdownToAnsi(text) {
|
|
293
|
-
const width = Math.max(terminalWidth() - 2,
|
|
311
|
+
const width = Math.max(terminalWidth() - 2, 1);
|
|
294
312
|
const rawLines = text.split("\n");
|
|
295
313
|
const out = [];
|
|
296
314
|
let inCode = false;
|
|
@@ -445,8 +463,8 @@ function panelString(content, title = "", subtitle = "", expand = true, borderSt
|
|
|
445
463
|
maxLineW = Math.max(maxLineW, plainLen(subtitle) + 4);
|
|
446
464
|
const termW = terminalWidth();
|
|
447
465
|
const boxW = expand
|
|
448
|
-
?
|
|
449
|
-
:
|
|
466
|
+
? fitBoxWidth(termW - 4, termW)
|
|
467
|
+
: fitBoxWidth(maxLineW + 4, termW);
|
|
450
468
|
const innerW = Math.max(boxW - 4, 1);
|
|
451
469
|
const embed = (text, align) => {
|
|
452
470
|
if (!text)
|
|
@@ -1072,7 +1090,7 @@ export function toolCallLabel(name, argumentsJson) {
|
|
|
1072
1090
|
arg = arg.trim().replace(/\s*\r?\n\s*/g, " ");
|
|
1073
1091
|
if (!arg)
|
|
1074
1092
|
return `\x1b[1;36m${display}\x1b[0m`;
|
|
1075
|
-
const cap = Math.max(Math.min(terminalWidth() - plainLen(display) - 4, MAX_COMMAND_DISPLAY_CHARS),
|
|
1093
|
+
const cap = Math.max(Math.min(terminalWidth() - plainLen(display) - 4, MAX_COMMAND_DISPLAY_CHARS), Math.min(terminalWidth() - plainLen(display) - 2, 8));
|
|
1076
1094
|
if (plainLen(arg) > cap)
|
|
1077
1095
|
arg = truncateStyled(arg, cap) + "…";
|
|
1078
1096
|
return `\x1b[1;36m${display}\x1b[0m(${arg})`;
|
|
@@ -1097,7 +1115,7 @@ export function formatToolResult(rawResult, failed) {
|
|
|
1097
1115
|
// Truncate to the terminal width (rows OR per-line length, whichever trips
|
|
1098
1116
|
// first) so long tool output — e.g. Glob paths — never runs off the screen.
|
|
1099
1117
|
const termW = terminalWidth();
|
|
1100
|
-
const lineMax = Math.max(termW - 4,
|
|
1118
|
+
const lineMax = Math.max(termW - 4, 1);
|
|
1101
1119
|
const allLines = clean.split("\n");
|
|
1102
1120
|
const lines = allLines
|
|
1103
1121
|
.slice(0, TOOL_RESULT_MAX_LINES)
|
|
@@ -1116,10 +1134,32 @@ export function formatToolResult(rawResult, failed) {
|
|
|
1116
1134
|
export const PROMPT_PLACEHOLDER = "Describe a coding task, or type /help for commands";
|
|
1117
1135
|
const MAX_PROMPT_DISPLAY_LINES = 12;
|
|
1118
1136
|
const MAX_PROMPT_PASTE_CHARS = 400;
|
|
1137
|
+
const MAX_PROMPT_CHARS = 500;
|
|
1119
1138
|
function shouldCollapsePaste(text) {
|
|
1120
1139
|
return (text.split("\n").length > MAX_PROMPT_DISPLAY_LINES ||
|
|
1121
1140
|
text.length > MAX_PROMPT_PASTE_CHARS);
|
|
1122
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Effective character count of a prompt for its length limit. Each non-collapsed
|
|
1144
|
+
* char counts as one; a collapsed pasted block counts as a single char.
|
|
1145
|
+
*/
|
|
1146
|
+
export function promptCharCount(buffer, pasteSpans) {
|
|
1147
|
+
if (!buffer)
|
|
1148
|
+
return 0;
|
|
1149
|
+
const spans = [...pasteSpans].sort((a, b) => a[0] - b[0]);
|
|
1150
|
+
let count = 0;
|
|
1151
|
+
let last = 0;
|
|
1152
|
+
for (const [s, e] of spans) {
|
|
1153
|
+
if (s > last)
|
|
1154
|
+
count += s - last;
|
|
1155
|
+
const seg = buffer.slice(s, e);
|
|
1156
|
+
count += shouldCollapsePaste(seg) ? 1 : e - s;
|
|
1157
|
+
last = e;
|
|
1158
|
+
}
|
|
1159
|
+
if (last < buffer.length)
|
|
1160
|
+
count += buffer.length - last;
|
|
1161
|
+
return count;
|
|
1162
|
+
}
|
|
1123
1163
|
export function cursorLineCol(buffer, cursor) {
|
|
1124
1164
|
const before = buffer.slice(0, cursor);
|
|
1125
1165
|
const lines = before.split("\n");
|
|
@@ -1238,7 +1278,7 @@ function wrapRuns(runs, width) {
|
|
|
1238
1278
|
}
|
|
1239
1279
|
export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor, hints) {
|
|
1240
1280
|
const termW = terminalWidth();
|
|
1241
|
-
const boxW =
|
|
1281
|
+
const boxW = fitBoxWidth(termW - 4, termW);
|
|
1242
1282
|
const innerW = boxW - 4;
|
|
1243
1283
|
const runs = [];
|
|
1244
1284
|
runs.push({ text: prefix, style: "\x1b[90m" });
|
|
@@ -1536,14 +1576,32 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1536
1576
|
draft = { buffer, cursor, spans: pasteSpans.slice() };
|
|
1537
1577
|
histIdx = hist.length;
|
|
1538
1578
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1579
|
+
// Non-collapsing pastes get a leading space when inserted after non-empty,
|
|
1580
|
+
// non-whitespace text (never when a space is already present). The space is
|
|
1581
|
+
// inserted as plain text (outside the paste span) so display spacing isn't
|
|
1582
|
+
// doubled.
|
|
1583
|
+
let prefix = "";
|
|
1584
|
+
if (isPaste &&
|
|
1585
|
+
!shouldCollapsePaste(text) &&
|
|
1586
|
+
cursor > 0 &&
|
|
1587
|
+
!endsWithWs(buffer.slice(0, cursor))) {
|
|
1588
|
+
prefix = " ";
|
|
1589
|
+
}
|
|
1590
|
+
const pasteStart = cursor + prefix.length;
|
|
1591
|
+
const insertLen = prefix.length + text.length;
|
|
1592
|
+
const newBuffer = buffer.slice(0, cursor) + prefix + text + buffer.slice(cursor);
|
|
1593
|
+
const newSpans = shiftSpansAfterInsert(cursor, insertLen);
|
|
1542
1594
|
if (isPaste) {
|
|
1543
|
-
|
|
1544
|
-
|
|
1595
|
+
newSpans.push([pasteStart, pasteStart + text.length]);
|
|
1596
|
+
newSpans.sort((a, b) => a[0] - b[0]);
|
|
1597
|
+
}
|
|
1598
|
+
// Enforce the 500-char effective limit; a collapsed block counts as one.
|
|
1599
|
+
if (promptCharCount(newBuffer, newSpans) > MAX_PROMPT_CHARS) {
|
|
1600
|
+
return;
|
|
1545
1601
|
}
|
|
1546
|
-
|
|
1602
|
+
buffer = newBuffer;
|
|
1603
|
+
pasteSpans = newSpans;
|
|
1604
|
+
cursor += insertLen;
|
|
1547
1605
|
};
|
|
1548
1606
|
const spanCollapsed = (span) => {
|
|
1549
1607
|
return shouldCollapsePaste(buffer.slice(span[0], span[1]));
|