@oxecli/oxe 1.0.77 → 1.0.79
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 +10 -4
- package/dist/tools.js +13 -5
- package/dist/ui.js +56 -0
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -577,12 +577,16 @@ export class InferenceEngine {
|
|
|
577
577
|
for (const c of calls) {
|
|
578
578
|
this.workActive = false;
|
|
579
579
|
const started = toolCallLabel(c.name, c.arguments);
|
|
580
|
+
// Print the tool label the instant the command starts, then show a
|
|
581
|
+
// "⎿ Working..." dots line that swaps to the real result in place.
|
|
582
|
+
process.stdout.write(`${started}\n`);
|
|
580
583
|
const toolSpinner = new Spinner();
|
|
581
|
-
toolSpinner.
|
|
584
|
+
toolSpinner.startDots("\x1b[90m⎿\x1b[0m Working");
|
|
582
585
|
const rawResult = await this.runTool(c.name, c.arguments);
|
|
583
|
-
|
|
584
|
-
|
|
586
|
+
if (this.interrupted) {
|
|
587
|
+
toolSpinner.stop();
|
|
585
588
|
throw new Error("interrupt");
|
|
589
|
+
}
|
|
586
590
|
const failed = toolOutputFailed(c.name, rawResult);
|
|
587
591
|
const action = formatToolResult(rawResult, failed);
|
|
588
592
|
story.push({
|
|
@@ -591,7 +595,9 @@ export class InferenceEngine {
|
|
|
591
595
|
text: action,
|
|
592
596
|
status: failed ? "failed" : "ok",
|
|
593
597
|
});
|
|
594
|
-
|
|
598
|
+
// Swap the "⎿ Working..." line for the real result, then a blank line.
|
|
599
|
+
toolSpinner.replaceWith(action);
|
|
600
|
+
process.stdout.write(`\n`);
|
|
595
601
|
// Any diff the tool produced is flushed only now, after the spinner
|
|
596
602
|
// has stopped, so the box renders cleanly below the action line.
|
|
597
603
|
flushPendingDiffOutput();
|
package/dist/tools.js
CHANGED
|
@@ -133,14 +133,22 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
133
133
|
const num = v.kind === "+" ? v.newNum : v.oldNum;
|
|
134
134
|
const numStr = num ? String(num).padStart(numW, " ") : gap;
|
|
135
135
|
if (v.kind === "+" || v.kind === "-") {
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
136
|
+
const isAdd = v.kind === "+";
|
|
137
|
+
const bg = isAdd ? "\x1b[48;2;2;40;0m" : "\x1b[48;2;61;1;0m";
|
|
138
|
+
const fg = isAdd ? "\x1b[38;2;80;200;80m" : "\x1b[38;2;220;90;90m";
|
|
139
|
+
const sign = isAdd ? "+" : "-";
|
|
140
|
+
// Changed content renders normal white with syntax highlighting;
|
|
141
|
+
// re-apply the white base after each token reset.
|
|
142
|
+
const raw = highlightCodeLine(v.body);
|
|
143
|
+
const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[97m"), bodyMax);
|
|
139
144
|
const fill = Math.max(0, termW - numW - 4 - plainLen(body));
|
|
140
|
-
pendingDiffOutput.push(`${bg}
|
|
145
|
+
pendingDiffOutput.push(`${bg}${fg}${numStr} ${sign} \x1b[0m\x1b[97m${body}${" ".repeat(fill)}\x1b[0m`);
|
|
141
146
|
}
|
|
142
147
|
else {
|
|
143
|
-
const
|
|
148
|
+
const raw = v.kind === "\\" ? v.body : highlightCodeLine(v.body);
|
|
149
|
+
// highlightCodeLine resets to default after each token; re-apply the dim
|
|
150
|
+
// gray base so plain text stays dim and only tokens show color.
|
|
151
|
+
const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[90m"), bodyMax);
|
|
144
152
|
pendingDiffOutput.push(`\x1b[90m${numStr} ${body}\x1b[0m`);
|
|
145
153
|
}
|
|
146
154
|
}
|
package/dist/ui.js
CHANGED
|
@@ -512,6 +512,7 @@ export function showCursor() {
|
|
|
512
512
|
}
|
|
513
513
|
}
|
|
514
514
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
515
|
+
const DOT_FRAMES = [". ", ".. ", "..."];
|
|
515
516
|
export class Spinner {
|
|
516
517
|
timer = null;
|
|
517
518
|
frame = 0;
|
|
@@ -520,6 +521,8 @@ export class Spinner {
|
|
|
520
521
|
enabled;
|
|
521
522
|
lastRendered = "";
|
|
522
523
|
lastLen = 0;
|
|
524
|
+
dotsText = "";
|
|
525
|
+
dotsFrame = 0;
|
|
523
526
|
constructor(enabled = true) {
|
|
524
527
|
this.enabled = enabled;
|
|
525
528
|
}
|
|
@@ -547,6 +550,59 @@ export class Spinner {
|
|
|
547
550
|
}, 80);
|
|
548
551
|
this.draw();
|
|
549
552
|
}
|
|
553
|
+
/** Dots-mode label: renders `text` (ANSI pre-colored by the caller) + a
|
|
554
|
+
* fixed 3-cell animated dots field. Each tick rewrites ONLY the dots cells
|
|
555
|
+
* via a cursor jump, so the rest of the line never flickers. Swap in the
|
|
556
|
+
* final label with replaceWith(). */
|
|
557
|
+
startDots(text) {
|
|
558
|
+
if (!this.enabled)
|
|
559
|
+
return;
|
|
560
|
+
if (this.timer)
|
|
561
|
+
clearInterval(this.timer);
|
|
562
|
+
this.dotsText = text;
|
|
563
|
+
this.dotsFrame = 0;
|
|
564
|
+
this.rows = 1;
|
|
565
|
+
this.lastRendered = "";
|
|
566
|
+
this.lastLen = plainLen(text) + 3;
|
|
567
|
+
this.writeDots(true);
|
|
568
|
+
this.timer = setInterval(() => {
|
|
569
|
+
this.dotsFrame = (this.dotsFrame + 1) % DOT_FRAMES.length;
|
|
570
|
+
this.writeDots(false);
|
|
571
|
+
}, 300);
|
|
572
|
+
}
|
|
573
|
+
writeDots(initial) {
|
|
574
|
+
const dots = `\x1b[1;36m${DOT_FRAMES[this.dotsFrame]}\x1b[0m`;
|
|
575
|
+
if (initial) {
|
|
576
|
+
process.stdout.write("\r" + this.dotsText + dots + "\r");
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
// Jump to the dots column (1-based) and rewrite only the 3-cell field.
|
|
580
|
+
process.stdout.write(`\x1b[${plainLen(this.dotsText) + 1}G` + dots + "\r");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Overwrite the dots line in place with `text` and advance to the next
|
|
584
|
+
* line, without erasing (so there is no flash). Multi-line text replaces
|
|
585
|
+
* the first line in place and prints the remaining lines below it. */
|
|
586
|
+
replaceWith(text) {
|
|
587
|
+
if (this.timer) {
|
|
588
|
+
clearInterval(this.timer);
|
|
589
|
+
this.timer = null;
|
|
590
|
+
}
|
|
591
|
+
if (!this.enabled) {
|
|
592
|
+
process.stdout.write(`${text}\n`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const parts = text.split("\n");
|
|
596
|
+
const first = parts[0];
|
|
597
|
+
const rest = parts.slice(1);
|
|
598
|
+
const plain = stripAnsi(first);
|
|
599
|
+
const pad = Math.max(0, this.lastLen - plain.length);
|
|
600
|
+
process.stdout.write("\r" + first + " ".repeat(pad) + "\r\n");
|
|
601
|
+
if (rest.length)
|
|
602
|
+
process.stdout.write(rest.join("\n") + "\n");
|
|
603
|
+
this.rows = 0;
|
|
604
|
+
this.lastLen = plain.length;
|
|
605
|
+
}
|
|
550
606
|
update(text) {
|
|
551
607
|
if (this.timer) {
|
|
552
608
|
this.render = () => text;
|