@oxecli/oxe 1.0.83 → 1.0.84

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
@@ -45,11 +45,13 @@ export class CLI {
45
45
  reasoning_effort: engine.reasoningEffort,
46
46
  });
47
47
  }
48
- printToolEntry(started, text, status) {
48
+ printToolEntry(started, text, status, diff = "") {
49
49
  const label = started && String(started).trim();
50
50
  const body = String(text).trim();
51
- if (label && stripAnsi(body).startsWith("⎿")) {
51
+ if (label && /^[⎿╰]/.test(stripAnsi(body))) {
52
52
  process.stdout.write(`${label}\n${body}\n`);
53
+ if (diff)
54
+ process.stdout.write(diff + "\n\n");
53
55
  return;
54
56
  }
55
57
  const icon = status === "failed" ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
@@ -213,7 +215,7 @@ export class CLI {
213
215
  this.printAssistantBlock(text);
214
216
  }
215
217
  else if (typ === "tool") {
216
- this.printToolEntry(e["started"], text, e["status"] ?? "");
218
+ this.printToolEntry(e["started"], text, e["status"] ?? "", e["diff"] ?? "");
217
219
  }
218
220
  else if (typ === "footer") {
219
221
  process.stdout.write(mutedMarkdown(text) + "\n");
package/dist/engine.js CHANGED
@@ -5,7 +5,7 @@ import { buildTools, truncateToolOutput, toolReadFile, toolWriteFile, toolEditFi
5
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
- import { flushPendingDiffOutput } from "./tools.js";
8
+ import { takePendingDiffOutput } from "./tools.js";
9
9
  // ---------------------------------------------------------------------------
10
10
  // Token estimation
11
11
  // ---------------------------------------------------------------------------
@@ -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();
@@ -592,17 +592,21 @@ export class InferenceEngine {
592
592
  }
593
593
  const failed = toolOutputFailed(c.name, rawResult);
594
594
  const action = formatToolResult(rawResult, failed);
595
+ const diff = takePendingDiffOutput();
595
596
  story.push({
596
597
  type: "tool",
597
598
  started,
598
599
  text: action,
599
600
  status: failed ? "failed" : "ok",
601
+ diff,
600
602
  });
601
- // Swap the " Working..." line for the real result. If the tool
603
+ // Swap the " Working..." line for the real result. If the tool
602
604
  // produced a diff it renders directly below the action line with no
603
605
  // gap; otherwise a blank line separates the action from what follows.
604
606
  toolSpinner.replaceWith(action);
605
- if (!flushPendingDiffOutput())
607
+ if (diff)
608
+ process.stdout.write(diff + "\n\n");
609
+ else
606
610
  process.stdout.write("\n");
607
611
  const truncatedResult = c.name === "read_file"
608
612
  ? truncateToolOutput(rawResult, max_read_file_stored_chars)
package/dist/tools.js CHANGED
@@ -34,16 +34,19 @@ function truncateDiffLine(line) {
34
34
  // Diff output is deferred (not written straight to stdout) so it doesn't
35
35
  // collide with the tool spinner that is still animating while the tool runs.
36
36
  const pendingDiffOutput = [];
37
- export function flushPendingDiffOutput() {
37
+ export function takePendingDiffOutput() {
38
38
  if (!pendingDiffOutput.length)
39
- return false;
40
- process.stdout.write(pendingDiffOutput.join("\n") + "\n\n");
39
+ return "";
40
+ const out = pendingDiffOutput.join("\n");
41
41
  pendingDiffOutput.length = 0;
42
- return true;
42
+ return out;
43
43
  }
44
44
  function displayDiff(pathName, oldContent, newContent) {
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").
47
+ const ind = " ";
45
48
  if (oldContent.length + newContent.length > max_diff_source_chars) {
46
- pendingDiffOutput.push(`\x1b[90m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
49
+ pendingDiffOutput.push(`\x1b[90m${ind}${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
47
50
  `(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
48
51
  return null;
49
52
  }
@@ -123,12 +126,12 @@ function displayDiff(pathName, oldContent, newContent) {
123
126
  maxNum = Math.max(maxNum, v.newNum);
124
127
  }
125
128
  const numW = String(maxNum).length;
126
- const bodyMax = Math.max(termW - numW - 4, 20);
129
+ const bodyMax = Math.max(termW - ind.length - numW - 4, 20);
127
130
  const gap = " ".repeat(numW);
128
131
  for (const v of visible) {
129
132
  if (v.kind === "~") {
130
133
  const body = truncateStyled(v.body, bodyMax);
131
- pendingDiffOutput.push(`\x1b[90m${gap} ${body}\x1b[0m`);
134
+ pendingDiffOutput.push(`\x1b[90m${ind}${gap} ${body}\x1b[0m`);
132
135
  continue;
133
136
  }
134
137
  const num = v.kind === "+" ? v.newNum : v.oldNum;
@@ -145,15 +148,17 @@ function displayDiff(pathName, oldContent, newContent) {
145
148
  // the whole body.
146
149
  const raw = highlightCodeLine(v.body);
147
150
  const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
148
- const fill = Math.max(0, termW - numW - 4 - plainLen(body));
149
- pendingDiffOutput.push(`${bg}${fg}${numStr} ${sign} \x1b[0m${bg}\x1b[97m${body}${bg}\x1b[97m${" ".repeat(fill)}\x1b[0m`);
151
+ const fill = Math.max(0, termW - ind.length - numW - 4 - plainLen(body));
152
+ pendingDiffOutput.push(`${ind}${bg}${fg}${numStr} ${sign} \x1b[0m${bg}\x1b[97m${body}${bg}\x1b[97m${" ".repeat(fill)}\x1b[0m`);
150
153
  }
151
154
  else {
152
155
  const raw = v.kind === "\\" ? v.body : highlightCodeLine(v.body);
153
- // highlightCodeLine resets to default after each token; re-apply the dim
154
- // gray base so plain text stays dim and only tokens show color.
155
- const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[90m"), bodyMax);
156
- pendingDiffOutput.push(`\x1b[90m${numStr} ${body}\x1b[0m`);
156
+ // Unchanged lines render their content normal white like the changed
157
+ // lines (only the number gutter stays dim). highlightCodeLine resets to
158
+ // default after each token, so re-apply the base after every reset.
159
+ const base = v.kind === "\\" ? "\x1b[90m" : "\x1b[97m";
160
+ const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${base}`), bodyMax);
161
+ pendingDiffOutput.push(`\x1b[90m${ind}${numStr} \x1b[0m${base}${body}\x1b[0m`);
157
162
  }
158
163
  }
159
164
  return { added, removed };
package/dist/ui.js CHANGED
@@ -240,14 +240,15 @@ export function markdownToAnsi(text) {
240
240
  const flushCodeBlock = () => {
241
241
  if (!codeBuffer.length && !codeLang)
242
242
  return;
243
- const badge = codeLang ? ` \x1b[1;36m${codeLang}\x1b[0m ` : " ";
244
- const barLen = Math.max(10, Math.min(width - plainLen(badge) - 4, 60));
245
- out.push(`\x1b[90m╭─${badge}${"─".repeat(barLen)}╮\x1b[0m`);
243
+ const badge = codeLang ? ` \x1b[1;36m${codeLang}\x1b[0m ` : "";
244
+ const barLen = Math.max(10, Math.min(width - plainLen(badge) - (codeLang ? 4 : 2), 60));
245
+ const topPrefix = codeLang ? "─" : "";
246
+ out.push(`\x1b[90m╭${topPrefix}${badge}${"─".repeat(barLen)}╮\x1b[0m`);
246
247
  for (const cline of codeBuffer) {
247
248
  const highlighted = highlightCodeLine(cline, codeLang);
248
249
  out.push(`\x1b[90m│\x1b[0m ${highlighted}`);
249
250
  }
250
- out.push(`\x1b[90m╰${"─".repeat(barLen + plainLen(badge) + 1)}╯\x1b[0m`);
251
+ out.push(`\x1b[90m╰${"─".repeat(barLen + plainLen(badge) + (codeLang ? 1 : 0))}╯\x1b[0m`);
251
252
  codeBuffer = [];
252
253
  codeLang = "";
253
254
  };
@@ -720,7 +721,7 @@ export function toolCallLabel(name, argumentsJson) {
720
721
  return `\x1b[1;36m${display}\x1b[0m(${arg})`;
721
722
  }
722
723
  /**
723
- * Second line of a tool entry: `⎿ Done` on a quiet success, otherwise the
724
+ * Second line of a tool entry: `╰ Done` on a quiet success, otherwise the
724
725
  * tool's output / error, collapsed and truncated so it never floods the screen.
725
726
  */
726
727
  export function formatToolResult(rawResult, failed) {
@@ -729,7 +730,7 @@ export function formatToolResult(rawResult, failed) {
729
730
  .trim();
730
731
  const code = raw.match(/^exit code: (\d+)/)?.[1] ?? null;
731
732
  const clean = raw.replace(/^exit code: \d+\n?/, "").trim();
732
- const corner = "\x1b[90m⎿\x1b[0m ";
733
+ const corner = "\x1b[90m╰\x1b[0m ";
733
734
  if (!failed && (!clean || clean === "(no output)")) {
734
735
  return `${corner}\x1b[32mDone\x1b[0m`;
735
736
  }
@@ -871,10 +872,10 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
871
872
  const boxW = Math.max(termW - 4, 16);
872
873
  const innerW = boxW - 4;
873
874
  const runs = [];
874
- runs.push({ text: prefix, style: "\x1b[1;36m" });
875
+ runs.push({ text: prefix, style: "\x1b[90m" });
875
876
  runs.push({ text: " ", style: "" });
876
877
  if (!buffer) {
877
- runs.push({ text: PROMPT_CARET, style: "\x1b[1;36m" });
878
+ runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
878
879
  runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[90m" });
879
880
  }
880
881
  else {
@@ -933,14 +934,14 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
933
934
  runs.push({ text: disp, style: "\x1b[1;36m" });
934
935
  if (!endsWithWs(disp))
935
936
  runs.push({ text: " ", style: "" });
936
- runs.push({ text: PROMPT_CARET, style: "\x1b[1;36m" });
937
+ runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
937
938
  }
938
939
  else {
939
940
  const before = disp.slice(0, inside);
940
941
  const after = disp.slice(inside);
941
942
  if (before)
942
943
  runs.push({ text: before, style: "" });
943
- runs.push({ text: PROMPT_CARET, style: "\x1b[1;36m" });
944
+ runs.push({ text: PROMPT_CARET, style: "\x1b[90m" });
944
945
  if (after)
945
946
  runs.push({ text: after, style: "" });
946
947
  }
@@ -955,8 +956,8 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
955
956
  }
956
957
  const { rows, caretRow, caretCol } = wrapRuns(runs, innerW);
957
958
  const labelLen = plainLen(label);
958
- const topPad = Math.max(0, boxW - labelLen - 5);
959
- const top = `\x1b[90m╭─ \x1b[1m\x1b[36m${label}\x1b[0m\x1b[90m ${"─".repeat(topPad)}╮\x1b[0m`;
959
+ const topPad = Math.max(0, boxW - labelLen - 7);
960
+ const top = `\x1b[90m╭─── \x1b[1m\x1b[36m${label}\x1b[0m\x1b[90m ${"─".repeat(topPad)}╮\x1b[0m`;
960
961
  const body = [];
961
962
  for (const l of rows) {
962
963
  const plain = stripAnsi(l);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.83",
3
+ "version": "1.0.84",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },