@oxecli/oxe 1.0.82 → 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/LICENSE CHANGED
@@ -12,10 +12,16 @@ furnished to do so, subject to the following conditions:
12
12
  The above copyright notice and this permission notice shall be included in all
13
13
  copies or substantial portions of the Software.
14
14
 
15
+ Redistribution or publication of the Software, in original or modified form,
16
+ must retain this copyright notice and must not be presented, published, or
17
+ distributed under a different author's name or claimed as the redistributor's
18
+ own original work. Attribution to the original author ("Oxe") must be
19
+ preserved in any public release, fork, or derivative distribution.
20
+
15
21
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
22
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
23
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
24
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
25
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
26
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
27
+ SOFTWARE.
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");
@@ -376,7 +378,7 @@ export class CLI {
376
378
  const budget = max_context_tokens - context_overhead_margin;
377
379
  const pct = Math.max(0, Math.min(100, Math.round(((budget - used) / budget) * 100)));
378
380
  const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory, {
379
- left: "\x1b[90mPress \x1b[1;36mesc\x1b[0m\x1b[90m to interrupt\x1b[0m",
381
+ left: "\x1b[90mPress \x1b[1;36mCtrl+C\x1b[0m\x1b[90m to interrupt\x1b[0m",
380
382
  right: `\x1b[1;36m${pct}%\x1b[0m \x1b[90muntil auto-compact\x1b[0m`,
381
383
  });
382
384
  if (!input.trim())
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,18 +592,22 @@ 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, then a blank line.
603
+ // Swap the " Working..." line for the real result. If the tool
604
+ // produced a diff it renders directly below the action line with no
605
+ // gap; otherwise a blank line separates the action from what follows.
602
606
  toolSpinner.replaceWith(action);
603
- process.stdout.write(`\n`);
604
- // Any diff the tool produced is flushed only now, after the spinner
605
- // has stopped, so the box renders cleanly below the action line.
606
- flushPendingDiffOutput();
607
+ if (diff)
608
+ process.stdout.write(diff + "\n\n");
609
+ else
610
+ process.stdout.write("\n");
607
611
  const truncatedResult = c.name === "read_file"
608
612
  ? truncateToolOutput(rawResult, max_read_file_stored_chars)
609
613
  : truncateToolOutput(rawResult);
package/dist/tools.js CHANGED
@@ -34,15 +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;
40
- process.stdout.write(pendingDiffOutput.join("\n") + "\n\n");
39
+ return "";
40
+ const out = pendingDiffOutput.join("\n");
41
41
  pendingDiffOutput.length = 0;
42
+ return out;
42
43
  }
43
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 = " ";
44
48
  if (oldContent.length + newContent.length > max_diff_source_chars) {
45
- 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 ` +
46
50
  `(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
47
51
  return null;
48
52
  }
@@ -122,12 +126,12 @@ function displayDiff(pathName, oldContent, newContent) {
122
126
  maxNum = Math.max(maxNum, v.newNum);
123
127
  }
124
128
  const numW = String(maxNum).length;
125
- const bodyMax = Math.max(termW - numW - 4, 20);
129
+ const bodyMax = Math.max(termW - ind.length - numW - 4, 20);
126
130
  const gap = " ".repeat(numW);
127
131
  for (const v of visible) {
128
132
  if (v.kind === "~") {
129
133
  const body = truncateStyled(v.body, bodyMax);
130
- pendingDiffOutput.push(`\x1b[90m${gap} ${body}\x1b[0m`);
134
+ pendingDiffOutput.push(`\x1b[90m${ind}${gap} ${body}\x1b[0m`);
131
135
  continue;
132
136
  }
133
137
  const num = v.kind === "+" ? v.newNum : v.oldNum;
@@ -137,19 +141,24 @@ function displayDiff(pathName, oldContent, newContent) {
137
141
  const bg = isAdd ? "\x1b[48;2;2;40;0m" : "\x1b[48;2;61;1;0m";
138
142
  const fg = isAdd ? "\x1b[38;2;80;200;80m" : "\x1b[38;2;220;90;90m";
139
143
  const sign = isAdd ? "+" : "-";
140
- // Changed content renders normal white with syntax highlighting;
141
- // re-apply the white base after each token reset.
144
+ // The row background spans the full line width (number, sign, content,
145
+ // and trailing fill) so the row reads as one colored strip. Changed
146
+ // content renders normal white with syntax highlighting; re-apply the
147
+ // background + white base after each token reset so both hold across
148
+ // the whole body.
142
149
  const raw = highlightCodeLine(v.body);
143
- const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[97m"), bodyMax);
144
- const fill = Math.max(0, termW - numW - 4 - plainLen(body));
145
- pendingDiffOutput.push(`${bg}${fg}${numStr} ${sign} \x1b[0m\x1b[97m${body}${" ".repeat(fill)}\x1b[0m`);
150
+ const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
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`);
146
153
  }
147
154
  else {
148
155
  const raw = v.kind === "\\" ? v.body : highlightCodeLine(v.body);
149
- // highlightCodeLine resets to default after each token; re-apply the dim
150
- // gray base so plain text stays dim and only tokens show color.
151
- const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[90m"), bodyMax);
152
- 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`);
153
162
  }
154
163
  }
155
164
  return { added, removed };
@@ -403,7 +412,7 @@ export function toolWriteFile(pathName, content) {
403
412
  catch (err) {
404
413
  return `Error: ${err}`;
405
414
  }
406
- return `Wrote ${content.length.toLocaleString()} characters to ${pathName}`;
415
+ return `Wrote ${content.length.toLocaleString()} chars`;
407
416
  }
408
417
  // ---------------------------------------------------------------------------
409
418
  // edit_file
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.82",
3
+ "version": "1.0.84",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },