@oxecli/oxe 1.0.77 → 1.0.79

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/engine.js CHANGED
@@ -577,12 +577,16 @@ export class InferenceEngine {
577
577
  for (const c of calls) {
578
578
  this.workActive = false;
579
579
  const started = toolCallLabel(c.name, c.arguments);
580
+ // Print the tool label the instant the command starts, then show a
581
+ // "⎿ Working..." dots line that swaps to the real result in place.
582
+ process.stdout.write(`${started}\n`);
580
583
  const toolSpinner = new Spinner();
581
- toolSpinner.start(started);
584
+ toolSpinner.startDots("\x1b[90m⎿\x1b[0m Working");
582
585
  const rawResult = await this.runTool(c.name, c.arguments);
583
- toolSpinner.stop();
584
- if (this.interrupted)
586
+ if (this.interrupted) {
587
+ toolSpinner.stop();
585
588
  throw new Error("interrupt");
589
+ }
586
590
  const failed = toolOutputFailed(c.name, rawResult);
587
591
  const action = formatToolResult(rawResult, failed);
588
592
  story.push({
@@ -591,7 +595,9 @@ export class InferenceEngine {
591
595
  text: action,
592
596
  status: failed ? "failed" : "ok",
593
597
  });
594
- process.stdout.write(`${started}\n${action}\n\n`);
598
+ // Swap the "⎿ Working..." line for the real result, then a blank line.
599
+ toolSpinner.replaceWith(action);
600
+ process.stdout.write(`\n`);
595
601
  // Any diff the tool produced is flushed only now, after the spinner
596
602
  // has stopped, so the box renders cleanly below the action line.
597
603
  flushPendingDiffOutput();
package/dist/tools.js CHANGED
@@ -133,14 +133,22 @@ function displayDiff(pathName, oldContent, newContent) {
133
133
  const num = v.kind === "+" ? v.newNum : v.oldNum;
134
134
  const numStr = num ? String(num).padStart(numW, " ") : gap;
135
135
  if (v.kind === "+" || v.kind === "-") {
136
- const bg = v.kind === "+" ? "\x1b[42m" : "\x1b[41m";
137
- const sign = v.kind === "+" ? "+" : "-";
138
- const body = truncateStyled(v.body, bodyMax);
136
+ const isAdd = v.kind === "+";
137
+ const bg = isAdd ? "\x1b[48;2;2;40;0m" : "\x1b[48;2;61;1;0m";
138
+ const fg = isAdd ? "\x1b[38;2;80;200;80m" : "\x1b[38;2;220;90;90m";
139
+ const sign = isAdd ? "+" : "-";
140
+ // Changed content renders normal white with syntax highlighting;
141
+ // re-apply the white base after each token reset.
142
+ const raw = highlightCodeLine(v.body);
143
+ const body = truncateStyled(raw.replace(/\x1b\[0m/g, "\x1b[0m\x1b[97m"), bodyMax);
139
144
  const fill = Math.max(0, termW - numW - 4 - plainLen(body));
140
- pendingDiffOutput.push(`${bg}\x1b[97m${numStr} ${sign} ${body}${" ".repeat(fill)}\x1b[0m`);
145
+ pendingDiffOutput.push(`${bg}${fg}${numStr} ${sign} \x1b[0m\x1b[97m${body}${" ".repeat(fill)}\x1b[0m`);
141
146
  }
142
147
  else {
143
- const body = truncateStyled(v.kind === "\\" ? v.body : highlightCodeLine(v.body), bodyMax);
148
+ 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);
144
152
  pendingDiffOutput.push(`\x1b[90m${numStr} ${body}\x1b[0m`);
145
153
  }
146
154
  }
package/dist/ui.js CHANGED
@@ -512,6 +512,7 @@ export function showCursor() {
512
512
  }
513
513
  }
514
514
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
515
+ const DOT_FRAMES = [". ", ".. ", "..."];
515
516
  export class Spinner {
516
517
  timer = null;
517
518
  frame = 0;
@@ -520,6 +521,8 @@ export class Spinner {
520
521
  enabled;
521
522
  lastRendered = "";
522
523
  lastLen = 0;
524
+ dotsText = "";
525
+ dotsFrame = 0;
523
526
  constructor(enabled = true) {
524
527
  this.enabled = enabled;
525
528
  }
@@ -547,6 +550,59 @@ export class Spinner {
547
550
  }, 80);
548
551
  this.draw();
549
552
  }
553
+ /** Dots-mode label: renders `text` (ANSI pre-colored by the caller) + a
554
+ * fixed 3-cell animated dots field. Each tick rewrites ONLY the dots cells
555
+ * via a cursor jump, so the rest of the line never flickers. Swap in the
556
+ * final label with replaceWith(). */
557
+ startDots(text) {
558
+ if (!this.enabled)
559
+ return;
560
+ if (this.timer)
561
+ clearInterval(this.timer);
562
+ this.dotsText = text;
563
+ this.dotsFrame = 0;
564
+ this.rows = 1;
565
+ this.lastRendered = "";
566
+ this.lastLen = plainLen(text) + 3;
567
+ this.writeDots(true);
568
+ this.timer = setInterval(() => {
569
+ this.dotsFrame = (this.dotsFrame + 1) % DOT_FRAMES.length;
570
+ this.writeDots(false);
571
+ }, 300);
572
+ }
573
+ writeDots(initial) {
574
+ const dots = `\x1b[1;36m${DOT_FRAMES[this.dotsFrame]}\x1b[0m`;
575
+ if (initial) {
576
+ process.stdout.write("\r" + this.dotsText + dots + "\r");
577
+ }
578
+ else {
579
+ // Jump to the dots column (1-based) and rewrite only the 3-cell field.
580
+ process.stdout.write(`\x1b[${plainLen(this.dotsText) + 1}G` + dots + "\r");
581
+ }
582
+ }
583
+ /** Overwrite the dots line in place with `text` and advance to the next
584
+ * line, without erasing (so there is no flash). Multi-line text replaces
585
+ * the first line in place and prints the remaining lines below it. */
586
+ replaceWith(text) {
587
+ if (this.timer) {
588
+ clearInterval(this.timer);
589
+ this.timer = null;
590
+ }
591
+ if (!this.enabled) {
592
+ process.stdout.write(`${text}\n`);
593
+ return;
594
+ }
595
+ const parts = text.split("\n");
596
+ const first = parts[0];
597
+ const rest = parts.slice(1);
598
+ const plain = stripAnsi(first);
599
+ const pad = Math.max(0, this.lastLen - plain.length);
600
+ process.stdout.write("\r" + first + " ".repeat(pad) + "\r\n");
601
+ if (rest.length)
602
+ process.stdout.write(rest.join("\n") + "\n");
603
+ this.rows = 0;
604
+ this.lastLen = plain.length;
605
+ }
550
606
  update(text) {
551
607
  if (this.timer) {
552
608
  this.render = () => text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.77",
3
+ "version": "1.0.79",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },