@oxecli/oxe 1.0.84 → 1.0.86

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
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { loadOrPrompt, default_reasoning_effort, max_action_chars, max_resume_history_items, runtimeOsSummary, max_context_tokens, context_overhead_margin, } from "./config.js";
5
5
  import { InferenceEngine, estimateTokens } from "./engine.js";
6
6
  import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, stripOrphanCalls, toolOutputFailed, } from "./sessions.js";
7
- import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, toolCallLabel, formatToolResult, truncateEllipsis, stripAnsi, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, } from "./ui.js";
7
+ import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, toolCallLabel, formatToolResult, truncateEllipsis, stripAnsi, plainLen, truncateStyled, terminalWidth, TOOL_RESULT_MAX_LINES, MAX_COMMAND_DISPLAY_CHARS, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, } from "./ui.js";
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const require = createRequire(import.meta.url);
10
10
  const packageInfo = require("../package.json");
@@ -49,14 +49,32 @@ export class CLI {
49
49
  const label = started && String(started).trim();
50
50
  const body = String(text).trim();
51
51
  if (label && /^[⎿╰]/.test(stripAnsi(body))) {
52
- process.stdout.write(`${label}\n${body}\n`);
53
- if (diff)
54
- process.stdout.write(diff + "\n\n");
52
+ // The result body was formatted against the terminal width at tool time,
53
+ // but the window may be narrower on resume — re-truncate defensively so
54
+ // long persisted lines never run off the screen.
55
+ const lineMax = Math.max(terminalWidth() - 4, 20);
56
+ const allLines = body.split("\n");
57
+ const lines = allLines
58
+ .slice(0, TOOL_RESULT_MAX_LINES)
59
+ .map((ln) => plainLen(ln) > lineMax ? truncateStyled(ln, lineMax) + "…\x1b[0m" : ln);
60
+ if (allLines.length > TOOL_RESULT_MAX_LINES) {
61
+ lines.push("\x1b[90m…\x1b[0m");
62
+ }
63
+ const labelLine = plainLen(label) > lineMax ? truncateStyled(label, lineMax) + "…\x1b[0m" : label;
64
+ process.stdout.write(`${labelLine}\n${lines.join("\n")}\n`);
65
+ if (diff) {
66
+ const diffLines = diff.split("\n").map((ln) => plainLen(ln) > lineMax ? truncateStyled(ln, lineMax) + "…\x1b[0m" : ln);
67
+ process.stdout.write(diffLines.join("\n") + "\n\n");
68
+ }
55
69
  return;
56
70
  }
57
71
  const icon = status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
58
72
  const style = status === "failed" ? "\x1b[31m" : "\x1b[32m";
59
- process.stdout.write(`${icon} ${style}${truncateEllipsis(body, max_action_chars, "text")}\x1b[0m\n`);
73
+ const truncated = truncateEllipsis(body, Math.min(max_action_chars, MAX_COMMAND_DISPLAY_CHARS), "text");
74
+ // truncateEllipsis appends a "…(text truncated: N chars total)" suffix, so
75
+ // re-truncate to the current width to keep the legacy action on one row.
76
+ const displayMax = Math.max(terminalWidth() - 6, 20);
77
+ process.stdout.write(`${icon} ${style}${truncateStyled(truncated, displayMax)}\x1b[0m\n`);
60
78
  }
61
79
  printAssistantBlock(text) {
62
80
  const rendered = aiMarkdown(text);
package/dist/engine.js CHANGED
@@ -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
- // "Working..." dots line that swaps to the real result in place.
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╰\x1b[0m Working");
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();
@@ -600,7 +600,7 @@ export class InferenceEngine {
600
600
  status: failed ? "failed" : "ok",
601
601
  diff,
602
602
  });
603
- // Swap the "Working..." line for the real result. If the tool
603
+ // Swap the "╰─ Working..." line for the real result. If the tool
604
604
  // produced a diff it renders directly below the action line with no
605
605
  // gap; otherwise a blank line separates the action from what follows.
606
606
  toolSpinner.replaceWith(action);
package/dist/tools.js CHANGED
@@ -43,7 +43,7 @@ export function takePendingDiffOutput() {
43
43
  }
44
44
  function displayDiff(pathName, oldContent, newContent) {
45
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").
46
+ // tool result text above it ("╰─ Wrote N chars").
47
47
  const ind = " ";
48
48
  if (oldContent.length + newContent.length > max_diff_source_chars) {
49
49
  pendingDiffOutput.push(`\x1b[90m${ind}${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
package/dist/ui.js CHANGED
@@ -681,10 +681,13 @@ const TOOL_DISPLAY_NAMES = {
681
681
  grep: "Grep",
682
682
  load_skill: "Load",
683
683
  };
684
- const TOOL_RESULT_MAX_CHARS = 400;
685
- const TOOL_RESULT_MAX_LINES = 6;
686
- const TOOL_RESULT_MAX_LINE_CHARS = 160;
687
- /** First line of a tool entry: `Bash(rm -f "…")` — cyan tool name, primary arg in parens. */
684
+ export const TOOL_RESULT_MAX_LINES = 6;
685
+ /** Cap on the visible command/arg shown in a tool label, so the entry always
686
+ * fits on a single row regardless of terminal width. */
687
+ export const MAX_COMMAND_DISPLAY_CHARS = 100;
688
+ /** First line of a tool entry: `Bash(rm -f "…")` — cyan tool name, primary arg
689
+ * in parens. Newlines collapse to spaces and the arg is width-capped so the
690
+ * label never wraps past one row. */
688
691
  export function toolCallLabel(name, argumentsJson) {
689
692
  let args = {};
690
693
  try {
@@ -713,15 +716,16 @@ export function toolCallLabel(name, argumentsJson) {
713
716
  arg = String(args["skill_name"] ?? "");
714
717
  break;
715
718
  }
716
- arg = arg.trim();
719
+ arg = arg.trim().replace(/\s*\r?\n\s*/g, " ");
717
720
  if (!arg)
718
721
  return `\x1b[1;36m${display}\x1b[0m`;
719
- if (arg.length > 400)
720
- arg = arg.slice(0, 400) + "…";
722
+ const cap = Math.max(Math.min(terminalWidth() - plainLen(display) - 4, MAX_COMMAND_DISPLAY_CHARS), 20);
723
+ if (plainLen(arg) > cap)
724
+ arg = truncateStyled(arg, cap) + "…";
721
725
  return `\x1b[1;36m${display}\x1b[0m(${arg})`;
722
726
  }
723
727
  /**
724
- * Second line of a tool entry: `╰ Done` on a quiet success, otherwise the
728
+ * Second line of a tool entry: `╰─ Done` on a quiet success, otherwise the
725
729
  * tool's output / error, collapsed and truncated so it never floods the screen.
726
730
  */
727
731
  export function formatToolResult(rawResult, failed) {
@@ -730,24 +734,22 @@ export function formatToolResult(rawResult, failed) {
730
734
  .trim();
731
735
  const code = raw.match(/^exit code: (\d+)/)?.[1] ?? null;
732
736
  const clean = raw.replace(/^exit code: \d+\n?/, "").trim();
733
- const corner = "\x1b[90m╰\x1b[0m ";
737
+ const corner = "\x1b[90m╰─\x1b[0m ";
734
738
  if (!failed && (!clean || clean === "(no output)")) {
735
739
  return `${corner}\x1b[32mDone\x1b[0m`;
736
740
  }
737
741
  if (failed && (!clean || clean === "(no output)") && code) {
738
742
  return `${corner}\x1b[31mexit code: ${code}\x1b[0m`;
739
743
  }
740
- let body = clean;
741
- const clipped = body.length > TOOL_RESULT_MAX_CHARS;
742
- if (clipped)
743
- body = body.slice(0, TOOL_RESULT_MAX_CHARS);
744
- const lines = body
745
- .split("\n")
744
+ // Truncate to the terminal width (rows OR per-line length, whichever trips
745
+ // first) so long tool output — e.g. Glob paths — never runs off the screen.
746
+ const termW = terminalWidth();
747
+ const lineMax = Math.max(termW - 4, 20);
748
+ const allLines = clean.split("\n");
749
+ const lines = allLines
746
750
  .slice(0, TOOL_RESULT_MAX_LINES)
747
- .map((ln) => ln.length > TOOL_RESULT_MAX_LINE_CHARS
748
- ? ln.slice(0, TOOL_RESULT_MAX_LINE_CHARS) + "…"
749
- : ln);
750
- if (clipped || body.split("\n").length > TOOL_RESULT_MAX_LINES)
751
+ .map((ln) => (ln.length > lineMax ? ln.slice(0, lineMax) + "…" : ln));
752
+ if (allLines.length > TOOL_RESULT_MAX_LINES)
751
753
  lines.push("…");
752
754
  const styleOpen = failed ? "\x1b[31m" : "\x1b[90m";
753
755
  const inner = lines
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.84",
3
+ "version": "1.0.86",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },