@oxecli/oxe 1.0.74 → 1.0.75

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, } 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, formatToolAction, truncateEllipsis, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, } from "./ui.js";
7
+ import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, toolCallLabel, formatToolResult, truncateEllipsis, stripAnsi, 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");
@@ -46,9 +46,15 @@ export class CLI {
46
46
  });
47
47
  }
48
48
  printToolEntry(started, text, status) {
49
+ const label = started && String(started).trim();
50
+ const body = String(text).trim();
51
+ if (label && stripAnsi(body).startsWith("⎿")) {
52
+ process.stdout.write(`${label}\n${body}\n`);
53
+ return;
54
+ }
49
55
  const icon = status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
50
56
  const style = status === "failed" ? "\x1b[31m" : "\x1b[32m";
51
- process.stdout.write(`${icon} ${style}${truncateEllipsis(String(text), max_action_chars, "text")}\x1b[0m\n`);
57
+ process.stdout.write(`${icon} ${style}${truncateEllipsis(body, max_action_chars, "text")}\x1b[0m\n`);
52
58
  }
53
59
  printAssistantBlock(text) {
54
60
  const rendered = aiMarkdown(text);
@@ -153,8 +159,8 @@ export class CLI {
153
159
  const argumentsJson = it["arguments"] ?? "";
154
160
  renders.push({
155
161
  kind: "tool",
156
- started: formatToolAction(name, argumentsJson, "started"),
157
- text: formatToolAction(name, argumentsJson, failed ? "failed" : "ok"),
162
+ started: toolCallLabel(name, argumentsJson),
163
+ text: formatToolResult(rawResult, failed),
158
164
  status: failed ? "failed" : "ok",
159
165
  });
160
166
  }
package/dist/engine.js CHANGED
@@ -2,7 +2,7 @@ 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 { getSystemPrompt } from "./system.js";
4
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";
5
+ import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, toolCallLabel, formatToolResult, renderPanel, Spinner, hideCursor, } from "./ui.js";
6
6
  import { reportUsage } from "./api.js";
7
7
  import { stripOrphanCalls, persistCompactionSummary, toolOutputFailed, } from "./sessions.js";
8
8
  import { flushPendingDiffOutput } from "./tools.js";
@@ -576,7 +576,7 @@ export class InferenceEngine {
576
576
  }
577
577
  for (const c of calls) {
578
578
  this.workActive = false;
579
- const started = formatToolAction(c.name, c.arguments, "started");
579
+ const started = toolCallLabel(c.name, c.arguments);
580
580
  const toolSpinner = new Spinner();
581
581
  toolSpinner.start(started);
582
582
  const rawResult = await this.runTool(c.name, c.arguments);
@@ -584,16 +584,14 @@ export class InferenceEngine {
584
584
  if (this.interrupted)
585
585
  throw new Error("interrupt");
586
586
  const failed = toolOutputFailed(c.name, rawResult);
587
- const action = formatToolAction(c.name, c.arguments, failed ? "failed" : "ok");
587
+ const action = formatToolResult(rawResult, failed);
588
588
  story.push({
589
589
  type: "tool",
590
590
  started,
591
591
  text: action,
592
592
  status: failed ? "failed" : "ok",
593
593
  });
594
- const icon = failed ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
595
- const actionStyle = failed ? "\x1b[31m" : "\x1b[32m";
596
- process.stdout.write(`${icon} ${actionStyle}${action}\x1b[0m\n\n`);
594
+ process.stdout.write(`${started}\n${action}\n\n`);
597
595
  // Any diff the tool produced is flushed only now, after the spinner
598
596
  // has stopped, so the box renders cleanly below the action line.
599
597
  flushPendingDiffOutput();
package/dist/ui.js CHANGED
@@ -613,9 +613,22 @@ export class Spinner {
613
613
  }
614
614
  }
615
615
  // ---------------------------------------------------------------------------
616
- // Tool Action Formatter
616
+ // Tool Call & Result Formatter
617
617
  // ---------------------------------------------------------------------------
618
- export function formatToolAction(name, argumentsJson, status = "ok") {
618
+ const TOOL_DISPLAY_NAMES = {
619
+ read_file: "Read",
620
+ write_file: "Write",
621
+ edit_file: "Edit",
622
+ bash: "Bash",
623
+ glob: "Glob",
624
+ grep: "Grep",
625
+ load_skill: "Load",
626
+ };
627
+ const TOOL_RESULT_MAX_CHARS = 400;
628
+ const TOOL_RESULT_MAX_LINES = 6;
629
+ const TOOL_RESULT_MAX_LINE_CHARS = 160;
630
+ /** First line of a tool entry: `Bash(rm -f "…")` — cyan tool name, primary arg in parens. */
631
+ export function toolCallLabel(name, argumentsJson) {
619
632
  let args = {};
620
633
  try {
621
634
  args = argumentsJson ? JSON.parse(argumentsJson) : {};
@@ -623,58 +636,67 @@ export function formatToolAction(name, argumentsJson, status = "ok") {
623
636
  catch {
624
637
  args = {};
625
638
  }
626
- const phrase = (verbOk, verbIng, verbFail, detail) => {
627
- if (status === "started")
628
- return `${verbIng} ${detail}`;
629
- if (status === "failed")
630
- return `${verbFail} ${detail}`;
631
- return `${verbOk} ${detail}`;
632
- };
633
- const p = args["path"] ?? "";
639
+ const display = TOOL_DISPLAY_NAMES[name] ??
640
+ (name ? name[0].toUpperCase() + name.slice(1) : "Tool");
641
+ let arg = "";
634
642
  switch (name) {
635
- case "read_file": {
636
- const start = args["start_line"];
637
- const end = args["end_line"];
638
- const span = start != null || end != null
639
- ? ` lines ${start ?? 1}-${end ?? "end"}`
640
- : "";
641
- return phrase("Read", "Reading", "Failed to read", `${p}${span}`);
642
- }
643
+ case "bash":
644
+ arg = String(args["command"] ?? "");
645
+ break;
646
+ case "read_file":
643
647
  case "write_file":
644
- return phrase("Wrote", "Writing", "Failed to write", `${p} · ${String(args["content"] ?? "").length.toLocaleString()} chars`);
645
- case "edit_file": {
646
- const suffix = args["replace_all"] ? " · replace all" : "";
647
- return phrase("Edited", "Editing", "Failed to edit", `${p}${suffix}`);
648
- }
649
- case "bash": {
650
- const cwd = args["cwd"];
651
- const location = cwd ? ` in ${cwd}` : "";
652
- const command = truncateEllipsis(String(args["command"] ?? ""), 400, "command");
653
- if (status === "started")
654
- return `Running command${location}: ${command}`;
655
- if (status === "failed")
656
- return `Command failed${location}: ${command}`;
657
- return `Ran command${location}: ${command}`;
658
- }
659
- case "glob": {
660
- const detail = String(args["pattern"] ?? "");
661
- if (status === "failed")
662
- return `Search failed for: ${detail}`;
663
- return phrase("Searched files for", "Searching files for", "Search failed for", detail);
664
- }
665
- case "grep": {
666
- const detail = String(args["pattern"] ?? "");
667
- if (status === "failed")
668
- return `Grep failed for: ${detail}`;
669
- return phrase("Grepped for", "Grepping for", "Grep failed for", detail);
670
- }
671
- case "load_skill": {
672
- const skill = String(args["skill_name"] ?? "");
673
- return phrase("Loaded skill", "Loading skill", "Failed to load skill", skill);
674
- }
675
- default:
676
- return phrase("Executed", "Executing", "Failed to execute", name);
648
+ case "edit_file":
649
+ arg = String(args["path"] ?? "");
650
+ break;
651
+ case "glob":
652
+ case "grep":
653
+ arg = String(args["pattern"] ?? "");
654
+ break;
655
+ case "load_skill":
656
+ arg = String(args["skill_name"] ?? "");
657
+ break;
677
658
  }
659
+ arg = arg.trim();
660
+ if (!arg)
661
+ return `\x1b[1;36m${display}\x1b[0m`;
662
+ if (arg.length > 400)
663
+ arg = arg.slice(0, 400) + "…";
664
+ return `\x1b[1;36m${display}\x1b[0m(${arg})`;
665
+ }
666
+ /**
667
+ * Second line of a tool entry: `⎿ Done` on a quiet success, otherwise the
668
+ * tool's output / error, collapsed and truncated so it never floods the screen.
669
+ */
670
+ export function formatToolResult(rawResult, failed) {
671
+ const raw = stripAnsi(rawResult || "")
672
+ .replace(/\r\n/g, "\n")
673
+ .trim();
674
+ const code = raw.match(/^exit code: (\d+)/)?.[1] ?? null;
675
+ const clean = raw.replace(/^exit code: \d+\n?/, "").trim();
676
+ const corner = "\x1b[90m⎿\x1b[0m ";
677
+ if (!failed && (!clean || clean === "(no output)")) {
678
+ return `${corner}\x1b[32mDone\x1b[0m`;
679
+ }
680
+ if (failed && (!clean || clean === "(no output)") && code) {
681
+ return `${corner}\x1b[31mexit code: ${code}\x1b[0m`;
682
+ }
683
+ let body = clean;
684
+ const clipped = body.length > TOOL_RESULT_MAX_CHARS;
685
+ if (clipped)
686
+ body = body.slice(0, TOOL_RESULT_MAX_CHARS);
687
+ const lines = body
688
+ .split("\n")
689
+ .slice(0, TOOL_RESULT_MAX_LINES)
690
+ .map((ln) => ln.length > TOOL_RESULT_MAX_LINE_CHARS
691
+ ? ln.slice(0, TOOL_RESULT_MAX_LINE_CHARS) + "…"
692
+ : ln);
693
+ if (clipped || body.split("\n").length > TOOL_RESULT_MAX_LINES)
694
+ lines.push("…");
695
+ const styleOpen = failed ? "\x1b[31m" : "\x1b[90m";
696
+ const inner = lines
697
+ .map((ln, i) => (i === 0 ? corner : " ") + styleOpen + ln)
698
+ .join("\n");
699
+ return `${inner}\x1b[0m`;
678
700
  }
679
701
  // ---------------------------------------------------------------------------
680
702
  // Prompt Editing & Interactive Input
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.74",
3
+ "version": "1.0.75",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },