@oxecli/oxe 1.0.12 → 1.0.14
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 +7 -1
- package/dist/engine.js +33 -7
- package/dist/oxe.js +10 -0
- package/dist/tools.js +1 -0
- package/dist/ui.js +45 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,13 @@ export class CLI {
|
|
|
46
46
|
showHelp() {
|
|
47
47
|
process.stdout.write("\n");
|
|
48
48
|
for (const [cmd, desc] of COMMAND_HELP) {
|
|
49
|
-
|
|
49
|
+
// The whole command column is bold; any `<arg>` placeholder (e.g. <n>,
|
|
50
|
+
// <level>) is additionally cyan — matching the original's
|
|
51
|
+
// `[bold cyan]<n>[/bold cyan]`. Pad based on the plain text length so the
|
|
52
|
+
// ANSI codes don't skew the column alignment.
|
|
53
|
+
const styled = cmd.replace(/(<[^>]+>)/g, "\x1b[36m$1\x1b[0m");
|
|
54
|
+
const pad = Math.max(18 - cmd.length, 0);
|
|
55
|
+
process.stdout.write(`\x1b[1m${styled}${" ".repeat(pad)}\x1b[0m\x1b[2m${desc}\x1b[0m\n`);
|
|
50
56
|
}
|
|
51
57
|
process.stdout.write("\n");
|
|
52
58
|
}
|
package/dist/engine.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import { max_output_tokens, max_empty_retries, max_agent_steps, max_context_tokens, context_overhead_margin, compact_keep_recent_turns, max_summary_source_chars, max_read_file_stored_chars, } from "./config.js";
|
|
3
3
|
import { SYSTEM_PROMPT } from "./system.js";
|
|
4
|
-
import { buildTools, truncateToolOutput,
|
|
5
|
-
import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, formatToolAction, renderPanel, Spinner, hideCursor,
|
|
4
|
+
import { buildTools, truncateToolOutput, toolReadFile, toolWriteFile, toolEditFile, toolBash, toolGlob, toolGrep, toolLoadSkill, } from "./tools.js";
|
|
5
|
+
import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, formatToolAction, renderPanel, Spinner, hideCursor, } from "./ui.js";
|
|
6
6
|
import { reportUsage } from "./api.js";
|
|
7
7
|
import { stripOrphanCalls, persistCompactionSummary, toolOutputFailed, } from "./sessions.js";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
@@ -83,10 +83,37 @@ export class InferenceEngine {
|
|
|
83
83
|
args = {};
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
// Dispatch by explicit named parameters. Do NOT use Object.values(args)
|
|
87
|
+
// (order-dependent): JSON object key order is not guaranteed, and when a
|
|
88
|
+
// model emits e.g. {content, path} for write_file the content would be
|
|
89
|
+
// mistaken for the path (turning JSX "/" into "\" on Windows). Binding
|
|
90
|
+
// each argument by name makes the tools robust to any key ordering.
|
|
91
|
+
let result;
|
|
92
|
+
switch (name) {
|
|
93
|
+
case "read_file":
|
|
94
|
+
result = toolReadFile(args.path, args.start_line, args.end_line);
|
|
95
|
+
break;
|
|
96
|
+
case "write_file":
|
|
97
|
+
result = toolWriteFile(args.path, args.content);
|
|
98
|
+
break;
|
|
99
|
+
case "edit_file":
|
|
100
|
+
result = toolEditFile(args.path, args.old_string, args.new_string, args.replace_all);
|
|
101
|
+
break;
|
|
102
|
+
case "bash":
|
|
103
|
+
result = await toolBash(args.command, args.timeout, args.cwd);
|
|
104
|
+
break;
|
|
105
|
+
case "glob":
|
|
106
|
+
result = toolGlob(args.pattern, args.path, args.limit);
|
|
107
|
+
break;
|
|
108
|
+
case "grep":
|
|
109
|
+
result = toolGrep(args.pattern, args.path, args.glob, args.limit);
|
|
110
|
+
break;
|
|
111
|
+
case "load_skill":
|
|
112
|
+
result = toolLoadSkill(args.skill_name);
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
return `Error: unknown tool '${name}'`;
|
|
116
|
+
}
|
|
90
117
|
return typeof result === "string" ? result : String(result);
|
|
91
118
|
}
|
|
92
119
|
catch (err) {
|
|
@@ -548,7 +575,6 @@ export class InferenceEngine {
|
|
|
548
575
|
}
|
|
549
576
|
finally {
|
|
550
577
|
this.inQuery = false;
|
|
551
|
-
showCursor();
|
|
552
578
|
if (stats["tokens"] > 0 && this.keyData) {
|
|
553
579
|
const userId = this.keyData["user_id"];
|
|
554
580
|
if (userId) {
|
package/dist/oxe.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { main } from "./cli.js";
|
|
2
2
|
export { main };
|
|
3
|
+
// The TUI keeps the terminal cursor hidden except inside a prompt field. Always
|
|
4
|
+
// restore it on exit so the shell isn't left with an invisible cursor.
|
|
5
|
+
process.on("exit", () => {
|
|
6
|
+
try {
|
|
7
|
+
process.stdout.write("\x1b[?25h");
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
/* ignore */
|
|
11
|
+
}
|
|
12
|
+
});
|
|
3
13
|
main().catch((err) => {
|
|
4
14
|
const msg = err?.message;
|
|
5
15
|
if (msg === "interrupt" || msg === "eof") {
|
package/dist/tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
4
4
|
import { structuredPatch } from "diff";
|
|
5
5
|
import { max_diff_source_chars, max_diff_lines, max_diff_context_lines, max_diff_line_chars, max_read_line_chars, max_read_file_bytes, max_read_lines, max_output_chars, max_bash_timeout_seconds, max_grep_file_bytes, strict_max_properties, ignoredDirs, } from "./config.js";
|
|
6
6
|
import { toolLoadSkill } from "./skills.js";
|
|
7
|
+
export { toolLoadSkill };
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// Natural sort + diff helpers
|
|
9
10
|
// ---------------------------------------------------------------------------
|
package/dist/ui.js
CHANGED
|
@@ -588,6 +588,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
588
588
|
let done = false;
|
|
589
589
|
let lastCursorRow = 0;
|
|
590
590
|
let lastTotalRows = 0;
|
|
591
|
+
let prevFrameLines = null;
|
|
591
592
|
// Erase the prompt region: a leading blank line + the box. Cursor sits at
|
|
592
593
|
// lastCursorRow inside the box; move up to the box's top border, erase the
|
|
593
594
|
// box rows, then erase the leading blank line, leaving the cursor on that
|
|
@@ -609,7 +610,6 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
609
610
|
return;
|
|
610
611
|
done = true;
|
|
611
612
|
process.stdin.setRawMode(false);
|
|
612
|
-
showCursor();
|
|
613
613
|
clearBox();
|
|
614
614
|
process.stdin.removeListener("keypress", onKeypress);
|
|
615
615
|
process.stdin.pause();
|
|
@@ -620,9 +620,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
620
620
|
};
|
|
621
621
|
const repaint = (isFirst = false) => {
|
|
622
622
|
const { frame, cursorRow, cursorCol, totalRows } = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor);
|
|
623
|
-
|
|
624
|
-
lastTotalRows = totalRows;
|
|
625
|
-
const frameLines = frame.split("\n");
|
|
623
|
+
const newLines = frame.split("\n");
|
|
626
624
|
if (isFirst) {
|
|
627
625
|
// The caller's content always ends with "\n", so the cursor sits at the
|
|
628
626
|
// start of a fresh (blank) line — that line is the leading blank
|
|
@@ -630,20 +628,47 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
630
628
|
// one line so the box starts below that blank; do NOT emit a second
|
|
631
629
|
// blank here (it caused a double gap above the box / on cancel).
|
|
632
630
|
process.stdout.write("\n");
|
|
631
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
632
|
+
process.stdout.write(newLines[i]);
|
|
633
|
+
if (i < newLines.length - 1)
|
|
634
|
+
process.stdout.write("\n");
|
|
635
|
+
}
|
|
636
|
+
prevFrameLines = newLines;
|
|
637
|
+
lastCursorRow = cursorRow;
|
|
638
|
+
lastTotalRows = totalRows;
|
|
639
|
+
// Cursor sits on the bottom border line; move it up to the input row.
|
|
640
|
+
const up = totalRows - 1 - cursorRow;
|
|
641
|
+
process.stdout.write(`\x1b[${up}A\r\x1b[${cursorCol}C`);
|
|
642
|
+
return;
|
|
633
643
|
}
|
|
634
644
|
else {
|
|
635
|
-
// Cursor currently sits at
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
645
|
+
// Cursor currently sits at the previous input position (lastCursorRow
|
|
646
|
+
// inside the box). Move up to the top border, then rewrite ONLY the
|
|
647
|
+
// lines whose content actually changed, leaving the static borders
|
|
648
|
+
// untouched — this avoids the whole-box flicker on every keystroke.
|
|
649
|
+
process.stdout.write(`\x1b[${lastCursorRow}A\r`);
|
|
650
|
+
const oldLines = prevFrameLines ?? [];
|
|
651
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
652
|
+
for (let i = 0; i < maxLen; i++) {
|
|
653
|
+
if (newLines[i] !== oldLines[i]) {
|
|
654
|
+
process.stdout.write("\r\x1b[K" + (newLines[i] ?? ""));
|
|
655
|
+
}
|
|
656
|
+
if (i < maxLen - 1)
|
|
657
|
+
process.stdout.write("\n");
|
|
658
|
+
}
|
|
659
|
+
// Cursor now sits at line (maxLen-1), column 0. Move it to the target
|
|
660
|
+
// input row (cursorRow), handling line-count changes in either direction.
|
|
661
|
+
const atLine = maxLen - 1;
|
|
662
|
+
if (atLine > cursorRow)
|
|
663
|
+
process.stdout.write(`\x1b[${atLine - cursorRow}A`);
|
|
664
|
+
else if (atLine < cursorRow)
|
|
665
|
+
process.stdout.write(`\x1b[${cursorRow - atLine}B`);
|
|
666
|
+
process.stdout.write(`\r\x1b[${cursorCol}C`);
|
|
667
|
+
prevFrameLines = newLines;
|
|
668
|
+
lastCursorRow = cursorRow;
|
|
669
|
+
lastTotalRows = totalRows;
|
|
670
|
+
return;
|
|
643
671
|
}
|
|
644
|
-
// Reposition the cursor inside the box.
|
|
645
|
-
const fromBottom = totalRows - 1 - cursorRow;
|
|
646
|
-
process.stdout.write(`\x1b[${fromBottom}A\r\x1b[${cursorCol}C`);
|
|
647
672
|
};
|
|
648
673
|
const insert = (text) => {
|
|
649
674
|
if (histIdx !== hist.length) {
|
|
@@ -770,6 +795,11 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
770
795
|
// User display + message text
|
|
771
796
|
// ---------------------------------------------------------------------------
|
|
772
797
|
export function userDisplayText(payload, pasteSpans) {
|
|
798
|
+
// Mirror the prompt field exactly: collapse ONLY segments that fall inside a
|
|
799
|
+
// paste span and exceed the collapse threshold (via splitBlocks). Content that
|
|
800
|
+
// the user typed (no paste span) is never collapsed in the prompt, so it must
|
|
801
|
+
// not be collapsed here either — keeping the echoed message consistent with
|
|
802
|
+
// what the prompt box displayed.
|
|
773
803
|
if (pasteSpans && pasteSpans.length) {
|
|
774
804
|
const segs = splitBlocks(payload, pasteSpans);
|
|
775
805
|
let out = "";
|
|
@@ -781,9 +811,6 @@ export function userDisplayText(payload, pasteSpans) {
|
|
|
781
811
|
}
|
|
782
812
|
return out;
|
|
783
813
|
}
|
|
784
|
-
if (shouldCollapsePaste(payload)) {
|
|
785
|
-
return `\x1b[1m\x1b[36m[Pasted text, ${payload.split("\n").length} lines]\x1b[0m`;
|
|
786
|
-
}
|
|
787
814
|
return payload;
|
|
788
815
|
}
|
|
789
816
|
export function collapseLabelText(text, spans) {
|