@oxecli/oxe 1.0.76 → 1.0.78
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 +9 -5
- package/dist/tools.js +27 -17
- package/dist/ui.js +50 -0
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -103,7 +103,7 @@ export class InferenceEngine {
|
|
|
103
103
|
result = await toolBash(args.command, args.timeout, args.cwd);
|
|
104
104
|
break;
|
|
105
105
|
case "glob":
|
|
106
|
-
result = toolGlob(args.pattern, args.path, args.limit);
|
|
106
|
+
result = toolGlob(args.pattern, args.path, args.limit, args.depth);
|
|
107
107
|
break;
|
|
108
108
|
case "grep":
|
|
109
109
|
result = toolGrep(args.pattern, args.path, args.glob, args.limit);
|
|
@@ -578,11 +578,12 @@ export class InferenceEngine {
|
|
|
578
578
|
this.workActive = false;
|
|
579
579
|
const started = toolCallLabel(c.name, c.arguments);
|
|
580
580
|
const toolSpinner = new Spinner();
|
|
581
|
-
toolSpinner.
|
|
581
|
+
toolSpinner.startDots("Running");
|
|
582
582
|
const rawResult = await this.runTool(c.name, c.arguments);
|
|
583
|
-
|
|
584
|
-
|
|
583
|
+
if (this.interrupted) {
|
|
584
|
+
toolSpinner.stop();
|
|
585
585
|
throw new Error("interrupt");
|
|
586
|
+
}
|
|
586
587
|
const failed = toolOutputFailed(c.name, rawResult);
|
|
587
588
|
const action = formatToolResult(rawResult, failed);
|
|
588
589
|
story.push({
|
|
@@ -591,7 +592,10 @@ export class InferenceEngine {
|
|
|
591
592
|
text: action,
|
|
592
593
|
status: failed ? "failed" : "ok",
|
|
593
594
|
});
|
|
594
|
-
|
|
595
|
+
// Replace the "Running..." line in place with the tool label so the
|
|
596
|
+
// transition is seamless, then print the result beneath it.
|
|
597
|
+
toolSpinner.replaceWith(started);
|
|
598
|
+
process.stdout.write(`${action}\n\n`);
|
|
595
599
|
// Any diff the tool produced is flushed only now, after the spinner
|
|
596
600
|
// has stopped, so the box renders cleanly below the action line.
|
|
597
601
|
flushPendingDiffOutput();
|
package/dist/tools.js
CHANGED
|
@@ -122,26 +122,29 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
122
122
|
maxNum = Math.max(maxNum, v.newNum);
|
|
123
123
|
}
|
|
124
124
|
const numW = String(maxNum).length;
|
|
125
|
-
const
|
|
126
|
-
const bodyMax = Math.max(termW - plainLen(indent) - numW - 4, 20);
|
|
125
|
+
const bodyMax = Math.max(termW - numW - 4, 20);
|
|
127
126
|
const gap = " ".repeat(numW);
|
|
128
127
|
for (const v of visible) {
|
|
129
128
|
if (v.kind === "~") {
|
|
130
129
|
const body = truncateStyled(v.body, bodyMax);
|
|
131
|
-
pendingDiffOutput.push(`\x1b[90m${
|
|
130
|
+
pendingDiffOutput.push(`\x1b[90m${gap} ${body}\x1b[0m`);
|
|
132
131
|
continue;
|
|
133
132
|
}
|
|
134
133
|
const num = v.kind === "+" ? v.newNum : v.oldNum;
|
|
135
134
|
const numStr = num ? String(num).padStart(numW, " ") : gap;
|
|
136
135
|
if (v.kind === "+" || v.kind === "-") {
|
|
137
|
-
const
|
|
136
|
+
const bg = v.kind === "+" ? "\x1b[48;2;2;40;0m" : "\x1b[48;2;61;1;0m";
|
|
138
137
|
const sign = v.kind === "+" ? "+" : "-";
|
|
139
138
|
const body = truncateStyled(v.body, bodyMax);
|
|
140
|
-
|
|
139
|
+
const fill = Math.max(0, termW - numW - 4 - plainLen(body));
|
|
140
|
+
pendingDiffOutput.push(`${bg}\x1b[97m${numStr} ${sign} ${body}${" ".repeat(fill)}\x1b[0m`);
|
|
141
141
|
}
|
|
142
142
|
else {
|
|
143
|
-
const
|
|
144
|
-
|
|
143
|
+
const raw = v.kind === "\\" ? v.body : highlightCodeLine(v.body);
|
|
144
|
+
// highlightCodeLine resets to default after each token; re-apply the dim
|
|
145
|
+
// gray base so plain text stays dim and only tokens show color.
|
|
146
|
+
const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[90m"), bodyMax);
|
|
147
|
+
pendingDiffOutput.push(`\x1b[90m${numStr} ${body}\x1b[0m`);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
return { added, removed };
|
|
@@ -629,7 +632,7 @@ function globMatch(name, pattern) {
|
|
|
629
632
|
.replace(/\?/g, ".");
|
|
630
633
|
return new RegExp(`^${regex}$`, "i").test(name);
|
|
631
634
|
}
|
|
632
|
-
function walkFiles(root, current, rel, parts, out) {
|
|
635
|
+
function walkFiles(root, current, rel, parts, out, depthLeft) {
|
|
633
636
|
let entries;
|
|
634
637
|
try {
|
|
635
638
|
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
@@ -643,8 +646,10 @@ function walkFiles(root, current, rel, parts, out) {
|
|
|
643
646
|
if (entry.isDirectory()) {
|
|
644
647
|
if (ignoredDirs.has(name))
|
|
645
648
|
continue;
|
|
649
|
+
if (depthLeft <= 0)
|
|
650
|
+
continue;
|
|
646
651
|
rel.push(name);
|
|
647
|
-
walkFiles(root, path.join(current, name), rel, parts, out);
|
|
652
|
+
walkFiles(root, path.join(current, name), rel, parts, out, depthLeft - 1);
|
|
648
653
|
rel.pop();
|
|
649
654
|
}
|
|
650
655
|
else if (entry.isFile()) {
|
|
@@ -661,13 +666,13 @@ function walkFiles(root, current, rel, parts, out) {
|
|
|
661
666
|
}
|
|
662
667
|
}
|
|
663
668
|
}
|
|
664
|
-
function rglobPruned(base, pattern) {
|
|
669
|
+
function rglobPruned(base, pattern, depth = 12) {
|
|
665
670
|
const norm = pattern.replace(/\\/g, "/");
|
|
666
671
|
const parts = norm.split("/").filter((p) => p !== "" && p !== ".");
|
|
667
672
|
const hasDotDot = parts.includes("..");
|
|
668
673
|
if (hasDotDot) {
|
|
669
674
|
const out = [];
|
|
670
|
-
const walk = (dir) => {
|
|
675
|
+
const walk = (dir, depthLeft) => {
|
|
671
676
|
let entries;
|
|
672
677
|
try {
|
|
673
678
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -678,8 +683,8 @@ function rglobPruned(base, pattern) {
|
|
|
678
683
|
for (const e of entries) {
|
|
679
684
|
const full = path.join(dir, e.name);
|
|
680
685
|
if (e.isDirectory()) {
|
|
681
|
-
if (!ignoredDirs.has(e.name))
|
|
682
|
-
walk(full);
|
|
686
|
+
if (!ignoredDirs.has(e.name) && depthLeft > 0)
|
|
687
|
+
walk(full, depthLeft - 1);
|
|
683
688
|
}
|
|
684
689
|
else if (e.isFile()) {
|
|
685
690
|
out.push([full, fs.statSync(full).size]);
|
|
@@ -690,7 +695,7 @@ function rglobPruned(base, pattern) {
|
|
|
690
695
|
out.push([base, fs.statSync(base).size]);
|
|
691
696
|
}
|
|
692
697
|
else {
|
|
693
|
-
walk(base);
|
|
698
|
+
walk(base, depth);
|
|
694
699
|
}
|
|
695
700
|
return out;
|
|
696
701
|
}
|
|
@@ -702,17 +707,18 @@ function rglobPruned(base, pattern) {
|
|
|
702
707
|
if (!parts.length)
|
|
703
708
|
return [];
|
|
704
709
|
const out = [];
|
|
705
|
-
walkFiles(base, base, [], ["**", ...parts], out);
|
|
710
|
+
walkFiles(base, base, [], ["**", ...parts], out, depth);
|
|
706
711
|
return out;
|
|
707
712
|
}
|
|
708
|
-
export function toolGlob(pattern, pathName = ".", limit = 200) {
|
|
713
|
+
export function toolGlob(pattern, pathName = ".", limit = 200, depth = 12) {
|
|
709
714
|
const base = sanitizePath(pathName) || ".";
|
|
710
715
|
if (!fs.existsSync(base))
|
|
711
716
|
return `Error: path not found: ${pathName}`;
|
|
712
717
|
const lim = typeof limit === "number" ? limit : parseInt(String(limit), 10) || 200;
|
|
718
|
+
const dep = Math.max(0, typeof depth === "number" ? depth : parseInt(String(depth), 10) || 12);
|
|
713
719
|
let matches;
|
|
714
720
|
try {
|
|
715
|
-
matches = rglobPruned(base, pattern);
|
|
721
|
+
matches = rglobPruned(base, pattern, dep);
|
|
716
722
|
}
|
|
717
723
|
catch {
|
|
718
724
|
return `Error: path not found: ${pathName}`;
|
|
@@ -891,6 +897,10 @@ export const RESPONSES_TOOLS = [
|
|
|
891
897
|
type: "integer",
|
|
892
898
|
description: "Max number of matches to return (default 200)",
|
|
893
899
|
},
|
|
900
|
+
depth: {
|
|
901
|
+
type: "integer",
|
|
902
|
+
description: "Max directory depth to descend (default 12)",
|
|
903
|
+
},
|
|
894
904
|
},
|
|
895
905
|
required: ["pattern"],
|
|
896
906
|
},
|
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,53 @@ export class Spinner {
|
|
|
547
550
|
}, 80);
|
|
548
551
|
this.draw();
|
|
549
552
|
}
|
|
553
|
+
/** Dots-mode label: renders `Running` + a fixed 3-cell animated dots field.
|
|
554
|
+
* Each tick rewrites ONLY the dots cells via a cursor jump, so the rest of
|
|
555
|
+
* the line never flickers. Swap in the final label with replaceWith(). */
|
|
556
|
+
startDots(text) {
|
|
557
|
+
if (!this.enabled)
|
|
558
|
+
return;
|
|
559
|
+
if (this.timer)
|
|
560
|
+
clearInterval(this.timer);
|
|
561
|
+
this.dotsText = text;
|
|
562
|
+
this.dotsFrame = 0;
|
|
563
|
+
this.rows = 1;
|
|
564
|
+
this.lastRendered = "";
|
|
565
|
+
this.lastLen = text.length + 3;
|
|
566
|
+
this.writeDots(true);
|
|
567
|
+
this.timer = setInterval(() => {
|
|
568
|
+
this.dotsFrame = (this.dotsFrame + 1) % DOT_FRAMES.length;
|
|
569
|
+
this.writeDots(false);
|
|
570
|
+
}, 300);
|
|
571
|
+
}
|
|
572
|
+
writeDots(initial) {
|
|
573
|
+
const label = `\x1b[1;36m${this.dotsText}\x1b[0m`;
|
|
574
|
+
const dots = `\x1b[1;36m${DOT_FRAMES[this.dotsFrame]}\x1b[0m`;
|
|
575
|
+
if (initial) {
|
|
576
|
+
process.stdout.write("\r" + label + 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[${this.dotsText.length + 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). */
|
|
585
|
+
replaceWith(text) {
|
|
586
|
+
if (this.timer) {
|
|
587
|
+
clearInterval(this.timer);
|
|
588
|
+
this.timer = null;
|
|
589
|
+
}
|
|
590
|
+
if (!this.enabled) {
|
|
591
|
+
process.stdout.write(`${text}\n`);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const plain = stripAnsi(text);
|
|
595
|
+
const pad = Math.max(0, this.lastLen - plain.length);
|
|
596
|
+
process.stdout.write("\r" + text + " ".repeat(pad) + "\r\n");
|
|
597
|
+
this.rows = 0;
|
|
598
|
+
this.lastLen = plain.length;
|
|
599
|
+
}
|
|
550
600
|
update(text) {
|
|
551
601
|
if (this.timer) {
|
|
552
602
|
this.render = () => text;
|