@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 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
- process.stdout.write(`\x1b[1m${cmd.padEnd(18)}\x1b[0m\x1b[2m${desc}\x1b[0m\n`);
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, TOOL_IMPLEMENTATIONS } from "./tools.js";
5
- import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, formatToolAction, renderPanel, Spinner, hideCursor, showCursor, } from "./ui.js";
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
- const impl = TOOL_IMPLEMENTATIONS[name];
87
- if (!impl)
88
- return `Error: unknown tool '${name}'`;
89
- const result = await impl(...Object.values(args));
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
- lastCursorRow = cursorRow;
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 cursorRow (inside the box). Move to the top
636
- // border, then clear+rewrite each line so old content is fully removed.
637
- process.stdout.write(`\x1b[${cursorRow}A`);
638
- }
639
- for (let i = 0; i < frameLines.length; i++) {
640
- process.stdout.write("\r\x1b[K" + frameLines[i]);
641
- if (i < frameLines.length - 1)
642
- process.stdout.write("\n");
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },