@oxecli/oxe 1.0.82 → 1.0.84
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/LICENSE +7 -1
- package/dist/cli.js +6 -4
- package/dist/engine.js +12 -8
- package/dist/tools.js +25 -16
- package/dist/ui.js +13 -12
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -12,10 +12,16 @@ furnished to do so, subject to the following conditions:
|
|
|
12
12
|
The above copyright notice and this permission notice shall be included in all
|
|
13
13
|
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
|
+
Redistribution or publication of the Software, in original or modified form,
|
|
16
|
+
must retain this copyright notice and must not be presented, published, or
|
|
17
|
+
distributed under a different author's name or claimed as the redistributor's
|
|
18
|
+
own original work. Attribution to the original author ("Oxe") must be
|
|
19
|
+
preserved in any public release, fork, or derivative distribution.
|
|
20
|
+
|
|
15
21
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
22
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
23
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
24
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
25
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
26
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
27
|
+
SOFTWARE.
|
package/dist/cli.js
CHANGED
|
@@ -45,11 +45,13 @@ export class CLI {
|
|
|
45
45
|
reasoning_effort: engine.reasoningEffort,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
printToolEntry(started, text, status) {
|
|
48
|
+
printToolEntry(started, text, status, diff = "") {
|
|
49
49
|
const label = started && String(started).trim();
|
|
50
50
|
const body = String(text).trim();
|
|
51
|
-
if (label && stripAnsi(body)
|
|
51
|
+
if (label && /^[⎿╰]/.test(stripAnsi(body))) {
|
|
52
52
|
process.stdout.write(`${label}\n${body}\n`);
|
|
53
|
+
if (diff)
|
|
54
|
+
process.stdout.write(diff + "\n\n");
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
55
57
|
const icon = status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
|
|
@@ -213,7 +215,7 @@ export class CLI {
|
|
|
213
215
|
this.printAssistantBlock(text);
|
|
214
216
|
}
|
|
215
217
|
else if (typ === "tool") {
|
|
216
|
-
this.printToolEntry(e["started"], text, e["status"] ?? "");
|
|
218
|
+
this.printToolEntry(e["started"], text, e["status"] ?? "", e["diff"] ?? "");
|
|
217
219
|
}
|
|
218
220
|
else if (typ === "footer") {
|
|
219
221
|
process.stdout.write(mutedMarkdown(text) + "\n");
|
|
@@ -376,7 +378,7 @@ export class CLI {
|
|
|
376
378
|
const budget = max_context_tokens - context_overhead_margin;
|
|
377
379
|
const pct = Math.max(0, Math.min(100, Math.round(((budget - used) / budget) * 100)));
|
|
378
380
|
const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory, {
|
|
379
|
-
left: "\x1b[90mPress \x1b[1;
|
|
381
|
+
left: "\x1b[90mPress \x1b[1;36mCtrl+C\x1b[0m\x1b[90m to interrupt\x1b[0m",
|
|
380
382
|
right: `\x1b[1;36m${pct}%\x1b[0m \x1b[90muntil auto-compact\x1b[0m`,
|
|
381
383
|
});
|
|
382
384
|
if (!input.trim())
|
package/dist/engine.js
CHANGED
|
@@ -5,7 +5,7 @@ import { buildTools, truncateToolOutput, toolReadFile, toolWriteFile, toolEditFi
|
|
|
5
5
|
import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, toolCallLabel, formatToolResult, renderPanel, Spinner, hideCursor, } from "./ui.js";
|
|
6
6
|
import { reportUsage } from "./api.js";
|
|
7
7
|
import { stripOrphanCalls, persistCompactionSummary, toolOutputFailed, } from "./sessions.js";
|
|
8
|
-
import {
|
|
8
|
+
import { takePendingDiffOutput } from "./tools.js";
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Token estimation
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
@@ -581,10 +581,10 @@ export class InferenceEngine {
|
|
|
581
581
|
this.queryCalledTool = true;
|
|
582
582
|
const started = toolCallLabel(c.name, c.arguments);
|
|
583
583
|
// Print the tool label the instant the command starts, then show a
|
|
584
|
-
// "
|
|
584
|
+
// "╰ Working..." dots line that swaps to the real result in place.
|
|
585
585
|
process.stdout.write(`${started}\n`);
|
|
586
586
|
const toolSpinner = new Spinner();
|
|
587
|
-
toolSpinner.startDots("\x1b[90m
|
|
587
|
+
toolSpinner.startDots("\x1b[90m╰\x1b[0m Working");
|
|
588
588
|
const rawResult = await this.runTool(c.name, c.arguments);
|
|
589
589
|
if (this.interrupted) {
|
|
590
590
|
toolSpinner.stop();
|
|
@@ -592,18 +592,22 @@ export class InferenceEngine {
|
|
|
592
592
|
}
|
|
593
593
|
const failed = toolOutputFailed(c.name, rawResult);
|
|
594
594
|
const action = formatToolResult(rawResult, failed);
|
|
595
|
+
const diff = takePendingDiffOutput();
|
|
595
596
|
story.push({
|
|
596
597
|
type: "tool",
|
|
597
598
|
started,
|
|
598
599
|
text: action,
|
|
599
600
|
status: failed ? "failed" : "ok",
|
|
601
|
+
diff,
|
|
600
602
|
});
|
|
601
|
-
// Swap the "
|
|
603
|
+
// Swap the "╰ Working..." line for the real result. If the tool
|
|
604
|
+
// produced a diff it renders directly below the action line with no
|
|
605
|
+
// gap; otherwise a blank line separates the action from what follows.
|
|
602
606
|
toolSpinner.replaceWith(action);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
+
if (diff)
|
|
608
|
+
process.stdout.write(diff + "\n\n");
|
|
609
|
+
else
|
|
610
|
+
process.stdout.write("\n");
|
|
607
611
|
const truncatedResult = c.name === "read_file"
|
|
608
612
|
? truncateToolOutput(rawResult, max_read_file_stored_chars)
|
|
609
613
|
: truncateToolOutput(rawResult);
|
package/dist/tools.js
CHANGED
|
@@ -34,15 +34,19 @@ function truncateDiffLine(line) {
|
|
|
34
34
|
// Diff output is deferred (not written straight to stdout) so it doesn't
|
|
35
35
|
// collide with the tool spinner that is still animating while the tool runs.
|
|
36
36
|
const pendingDiffOutput = [];
|
|
37
|
-
export function
|
|
37
|
+
export function takePendingDiffOutput() {
|
|
38
38
|
if (!pendingDiffOutput.length)
|
|
39
|
-
return;
|
|
40
|
-
|
|
39
|
+
return "";
|
|
40
|
+
const out = pendingDiffOutput.join("\n");
|
|
41
41
|
pendingDiffOutput.length = 0;
|
|
42
|
+
return out;
|
|
42
43
|
}
|
|
43
44
|
function displayDiff(pathName, oldContent, newContent) {
|
|
45
|
+
// Indent every diff row 3 columns so the diff's left edge lines up with the
|
|
46
|
+
// tool result text above it ("╰ Wrote N chars").
|
|
47
|
+
const ind = " ";
|
|
44
48
|
if (oldContent.length + newContent.length > max_diff_source_chars) {
|
|
45
|
-
pendingDiffOutput.push(`\x1b[90m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
|
|
49
|
+
pendingDiffOutput.push(`\x1b[90m${ind}${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
|
|
46
50
|
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
|
|
47
51
|
return null;
|
|
48
52
|
}
|
|
@@ -122,12 +126,12 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
122
126
|
maxNum = Math.max(maxNum, v.newNum);
|
|
123
127
|
}
|
|
124
128
|
const numW = String(maxNum).length;
|
|
125
|
-
const bodyMax = Math.max(termW - numW - 4, 20);
|
|
129
|
+
const bodyMax = Math.max(termW - ind.length - numW - 4, 20);
|
|
126
130
|
const gap = " ".repeat(numW);
|
|
127
131
|
for (const v of visible) {
|
|
128
132
|
if (v.kind === "~") {
|
|
129
133
|
const body = truncateStyled(v.body, bodyMax);
|
|
130
|
-
pendingDiffOutput.push(`\x1b[90m${gap} ${body}\x1b[0m`);
|
|
134
|
+
pendingDiffOutput.push(`\x1b[90m${ind}${gap} ${body}\x1b[0m`);
|
|
131
135
|
continue;
|
|
132
136
|
}
|
|
133
137
|
const num = v.kind === "+" ? v.newNum : v.oldNum;
|
|
@@ -137,19 +141,24 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
137
141
|
const bg = isAdd ? "\x1b[48;2;2;40;0m" : "\x1b[48;2;61;1;0m";
|
|
138
142
|
const fg = isAdd ? "\x1b[38;2;80;200;80m" : "\x1b[38;2;220;90;90m";
|
|
139
143
|
const sign = isAdd ? "+" : "-";
|
|
140
|
-
//
|
|
141
|
-
//
|
|
144
|
+
// The row background spans the full line width (number, sign, content,
|
|
145
|
+
// and trailing fill) so the row reads as one colored strip. Changed
|
|
146
|
+
// content renders normal white with syntax highlighting; re-apply the
|
|
147
|
+
// background + white base after each token reset so both hold across
|
|
148
|
+
// the whole body.
|
|
142
149
|
const raw = highlightCodeLine(v.body);
|
|
143
|
-
const body = truncateStyled(raw.replace(/\x1b\[0m/g,
|
|
144
|
-
const fill = Math.max(0, termW - numW - 4 - plainLen(body));
|
|
145
|
-
pendingDiffOutput.push(`${bg}${fg}${numStr} ${sign} \x1b[0m\x1b[97m${body}${" ".repeat(fill)}\x1b[0m`);
|
|
150
|
+
const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
|
|
151
|
+
const fill = Math.max(0, termW - ind.length - numW - 4 - plainLen(body));
|
|
152
|
+
pendingDiffOutput.push(`${ind}${bg}${fg}${numStr} ${sign} \x1b[0m${bg}\x1b[97m${body}${bg}\x1b[97m${" ".repeat(fill)}\x1b[0m`);
|
|
146
153
|
}
|
|
147
154
|
else {
|
|
148
155
|
const raw = v.kind === "\\" ? v.body : highlightCodeLine(v.body);
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
// Unchanged lines render their content normal white like the changed
|
|
157
|
+
// lines (only the number gutter stays dim). highlightCodeLine resets to
|
|
158
|
+
// default after each token, so re-apply the base after every reset.
|
|
159
|
+
const base = v.kind === "\\" ? "\x1b[90m" : "\x1b[97m";
|
|
160
|
+
const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${base}`), bodyMax);
|
|
161
|
+
pendingDiffOutput.push(`\x1b[90m${ind}${numStr} \x1b[0m${base}${body}\x1b[0m`);
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
164
|
return { added, removed };
|
|
@@ -403,7 +412,7 @@ export function toolWriteFile(pathName, content) {
|
|
|
403
412
|
catch (err) {
|
|
404
413
|
return `Error: ${err}`;
|
|
405
414
|
}
|
|
406
|
-
return `Wrote ${content.length.toLocaleString()}
|
|
415
|
+
return `Wrote ${content.length.toLocaleString()} chars`;
|
|
407
416
|
}
|
|
408
417
|
// ---------------------------------------------------------------------------
|
|
409
418
|
// edit_file
|
package/dist/ui.js
CHANGED
|
@@ -240,14 +240,15 @@ export function markdownToAnsi(text) {
|
|
|
240
240
|
const flushCodeBlock = () => {
|
|
241
241
|
if (!codeBuffer.length && !codeLang)
|
|
242
242
|
return;
|
|
243
|
-
const badge = codeLang ? ` \x1b[1;36m${codeLang}\x1b[0m ` : "
|
|
244
|
-
const barLen = Math.max(10, Math.min(width - plainLen(badge) - 4, 60));
|
|
245
|
-
|
|
243
|
+
const badge = codeLang ? ` \x1b[1;36m${codeLang}\x1b[0m ` : "";
|
|
244
|
+
const barLen = Math.max(10, Math.min(width - plainLen(badge) - (codeLang ? 4 : 2), 60));
|
|
245
|
+
const topPrefix = codeLang ? "─" : "";
|
|
246
|
+
out.push(`\x1b[90m╭${topPrefix}${badge}${"─".repeat(barLen)}╮\x1b[0m`);
|
|
246
247
|
for (const cline of codeBuffer) {
|
|
247
248
|
const highlighted = highlightCodeLine(cline, codeLang);
|
|
248
249
|
out.push(`\x1b[90m│\x1b[0m ${highlighted}`);
|
|
249
250
|
}
|
|
250
|
-
out.push(`\x1b[90m╰${"─".repeat(barLen + plainLen(badge) + 1)}╯\x1b[0m`);
|
|
251
|
+
out.push(`\x1b[90m╰${"─".repeat(barLen + plainLen(badge) + (codeLang ? 1 : 0))}╯\x1b[0m`);
|
|
251
252
|
codeBuffer = [];
|
|
252
253
|
codeLang = "";
|
|
253
254
|
};
|
|
@@ -720,7 +721,7 @@ export function toolCallLabel(name, argumentsJson) {
|
|
|
720
721
|
return `\x1b[1;36m${display}\x1b[0m(${arg})`;
|
|
721
722
|
}
|
|
722
723
|
/**
|
|
723
|
-
* Second line of a tool entry:
|
|
724
|
+
* Second line of a tool entry: `╰ Done` on a quiet success, otherwise the
|
|
724
725
|
* tool's output / error, collapsed and truncated so it never floods the screen.
|
|
725
726
|
*/
|
|
726
727
|
export function formatToolResult(rawResult, failed) {
|
|
@@ -729,7 +730,7 @@ export function formatToolResult(rawResult, failed) {
|
|
|
729
730
|
.trim();
|
|
730
731
|
const code = raw.match(/^exit code: (\d+)/)?.[1] ?? null;
|
|
731
732
|
const clean = raw.replace(/^exit code: \d+\n?/, "").trim();
|
|
732
|
-
const corner = "\x1b[90m
|
|
733
|
+
const corner = "\x1b[90m╰\x1b[0m ";
|
|
733
734
|
if (!failed && (!clean || clean === "(no output)")) {
|
|
734
735
|
return `${corner}\x1b[32mDone\x1b[0m`;
|
|
735
736
|
}
|
|
@@ -871,10 +872,10 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
871
872
|
const boxW = Math.max(termW - 4, 16);
|
|
872
873
|
const innerW = boxW - 4;
|
|
873
874
|
const runs = [];
|
|
874
|
-
runs.push({ text: prefix, style: "\x1b[
|
|
875
|
+
runs.push({ text: prefix, style: "\x1b[90m" });
|
|
875
876
|
runs.push({ text: " ", style: "" });
|
|
876
877
|
if (!buffer) {
|
|
877
|
-
runs.push({ text: PROMPT_CARET, style: "\x1b[
|
|
878
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
|
|
878
879
|
runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[90m" });
|
|
879
880
|
}
|
|
880
881
|
else {
|
|
@@ -933,14 +934,14 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
933
934
|
runs.push({ text: disp, style: "\x1b[1;36m" });
|
|
934
935
|
if (!endsWithWs(disp))
|
|
935
936
|
runs.push({ text: " ", style: "" });
|
|
936
|
-
runs.push({ text: PROMPT_CARET, style: "\x1b[
|
|
937
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
|
|
937
938
|
}
|
|
938
939
|
else {
|
|
939
940
|
const before = disp.slice(0, inside);
|
|
940
941
|
const after = disp.slice(inside);
|
|
941
942
|
if (before)
|
|
942
943
|
runs.push({ text: before, style: "" });
|
|
943
|
-
runs.push({ text: PROMPT_CARET, style: "\x1b[
|
|
944
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
|
|
944
945
|
if (after)
|
|
945
946
|
runs.push({ text: after, style: "" });
|
|
946
947
|
}
|
|
@@ -955,8 +956,8 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
955
956
|
}
|
|
956
957
|
const { rows, caretRow, caretCol } = wrapRuns(runs, innerW);
|
|
957
958
|
const labelLen = plainLen(label);
|
|
958
|
-
const topPad = Math.max(0, boxW - labelLen -
|
|
959
|
-
const top = `\x1b[90m
|
|
959
|
+
const topPad = Math.max(0, boxW - labelLen - 7);
|
|
960
|
+
const top = `\x1b[90m╭─── \x1b[1m\x1b[36m${label}\x1b[0m\x1b[90m ${"─".repeat(topPad)}╮\x1b[0m`;
|
|
960
961
|
const body = [];
|
|
961
962
|
for (const l of rows) {
|
|
962
963
|
const plain = stripAnsi(l);
|