@oxecli/oxe 1.0.7 → 1.0.8

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
@@ -15,6 +15,7 @@ export class CLI {
15
15
  story = [];
16
16
  sessionId = null;
17
17
  promptHistory = [];
18
+ interruptPending = false;
18
19
  constructor() {
19
20
  clearScreen();
20
21
  renderPanel("AI CODING AGENT", "", "Engine: Ready", false);
@@ -107,7 +108,8 @@ export class CLI {
107
108
  if (footer) {
108
109
  if (!footer.startsWith("("))
109
110
  footer = `(${footer})`;
110
- process.stdout.write("\n" + mutedMarkdown(footer) + "\n");
111
+ // Footer: labels white, only the backtick values grey (not the whole line).
112
+ process.stdout.write("\n" + aiMarkdown(footer) + "\n");
111
113
  }
112
114
  }
113
115
  else {
@@ -138,6 +140,10 @@ export class CLI {
138
140
  else if (typ === "tool") {
139
141
  this.printToolEntry(e["started"], text, e["status"] ?? "");
140
142
  }
143
+ else if (typ === "footer") {
144
+ // Footer: labels white, only the backtick values grey (not the whole line).
145
+ process.stdout.write(aiMarkdown(text) + "\n");
146
+ }
141
147
  else {
142
148
  process.stdout.write(mutedMarkdown(text) + "\n");
143
149
  }
@@ -210,6 +216,7 @@ export class CLI {
210
216
  const [input, spans] = await askBottomPrompt("You", "❯", this.promptHistory);
211
217
  if (!input.trim())
212
218
  continue;
219
+ this.interruptPending = false;
213
220
  this.promptHistory.push(input);
214
221
  const lower = input.trim().toLowerCase();
215
222
  if (lower === "/exit" || lower === "/quit") {
@@ -260,12 +267,35 @@ export class CLI {
260
267
  this.saveSession(engine);
261
268
  }
262
269
  catch (err) {
263
- if (err?.message === "interrupt" || err?.message === "eof") {
270
+ if (err?.message === "eof") {
264
271
  this.saveSession(engine);
265
272
  await engine.cleanupStoredResponses();
266
- process.stdout.write("\nClosing terminal session. Goodbye!\n");
273
+ process.stdout.write("\nSession closing via exit interrupt hook.\n");
267
274
  break;
268
275
  }
276
+ if (err?.message === "interrupt") {
277
+ const wasActive = engine.inQuery;
278
+ engine.inQuery = false;
279
+ // Drop the pending user message/story entry that was never run.
280
+ if (this.inputItems.length && this.inputItems[this.inputItems.length - 1]["role"] === "user") {
281
+ this.inputItems.pop();
282
+ }
283
+ if (this.story.length && this.story[this.story.length - 1]["type"] === "user") {
284
+ this.story.pop();
285
+ }
286
+ if (this.interruptPending) {
287
+ this.saveSession(engine);
288
+ await engine.cleanupStoredResponses();
289
+ process.stdout.write("\nClosing terminal session. Goodbye!\n");
290
+ break;
291
+ }
292
+ this.interruptPending = true;
293
+ process.stdout.write("\n");
294
+ process.stdout.write(wasActive
295
+ ? "\x1b[2mInterrupted. Press Ctrl+C again to exit, or type a new prompt.\x1b[0m\n"
296
+ : "\x1b[2mNo active response. Press Ctrl+C again to exit.\x1b[0m\n");
297
+ continue;
298
+ }
269
299
  throw err;
270
300
  }
271
301
  }
package/dist/engine.js CHANGED
@@ -400,7 +400,7 @@ export class InferenceEngine {
400
400
  const stats = { thinking: 0, tokens: 0, reasoning: 0, input: 0 };
401
401
  const queryStart = Date.now();
402
402
  const footerText = () => `(Thought for ${tickDuration(stats["thinking"])} · Worked for ${tickDuration((Date.now() - queryStart) / 1000)} · Used \`${stats["tokens"].toLocaleString()}\` tokens)`;
403
- const summary = () => mutedMarkdown(footerText());
403
+ const summary = () => aiMarkdown(footerText());
404
404
  const attachFooter = (items) => {
405
405
  const footer = footerText();
406
406
  for (let i = items.length - 1; i >= 0; i--) {
package/dist/ui.js CHANGED
@@ -306,16 +306,25 @@ export class Spinner {
306
306
  const rendered = mutedMarkdown(`${SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length]} ${this.text}`);
307
307
  const newRows = Math.max(1, displayRows(rendered));
308
308
  const clearRows = Math.max(this.rows, newRows, 1);
309
- if (this.rows > 0)
310
- process.stdout.write(`\x1b[${this.rows}A`);
309
+ // Cursor is always at the home position (col 0) after each draw. Clear
310
+ // `clearRows` lines downward from home, rewrite the content, then return
311
+ // the cursor to home (col 0) so the next frame overwrites in place.
311
312
  for (let i = 0; i < clearRows; i++) {
312
313
  process.stdout.write("\r\x1b[2K");
313
314
  if (i < clearRows - 1)
314
315
  process.stdout.write("\n");
315
316
  }
316
- if (clearRows > 0)
317
+ // Cursor is now `clearRows-1` lines below home; move back to home.
318
+ if (clearRows > 1)
317
319
  process.stdout.write(`\x1b[${clearRows - 1}A\r`);
320
+ else
321
+ process.stdout.write("\r");
318
322
  process.stdout.write(rendered);
323
+ // Return cursor to home (col 0) for the next draw.
324
+ if (newRows > 1)
325
+ process.stdout.write(`\x1b[${newRows - 1}A\r`);
326
+ else
327
+ process.stdout.write("\r");
319
328
  this.rows = newRows;
320
329
  }
321
330
  stop() {
@@ -324,14 +333,16 @@ export class Spinner {
324
333
  this.timer = null;
325
334
  }
326
335
  if (this.enabled && this.rows > 0) {
327
- process.stdout.write(`\x1b[${this.rows}A`);
336
+ // Cursor is at home (col 0); clear the rendered rows downward.
328
337
  for (let i = 0; i < this.rows; i++) {
329
338
  process.stdout.write("\r\x1b[2K");
330
339
  if (i < this.rows - 1)
331
340
  process.stdout.write("\n");
332
341
  }
333
- if (this.rows > 0)
334
- process.stdout.write(`\x1b[${this.rows - 1}A`);
342
+ if (this.rows > 1)
343
+ process.stdout.write(`\x1b[${this.rows - 1}A\r`);
344
+ else
345
+ process.stdout.write("\r");
335
346
  }
336
347
  this.rows = 0;
337
348
  }
@@ -577,17 +588,12 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
577
588
  let done = false;
578
589
  let lastCursorRow = 0;
579
590
  let lastTotalRows = 0;
580
- const finish = (resolveVal) => {
581
- if (done)
582
- return;
583
- done = true;
584
- process.stdin.setRawMode(false);
585
- showCursor();
586
- // Erase the prompt region: a leading blank line + the box. Cursor sits at
587
- // lastCursorRow inside the box; move up to the box's top border, erase the
588
- // box rows, then erase the leading blank line, leaving the cursor on that
589
- // blank row. The caller's single leading newline then yields exactly ONE
590
- // blank row before the echoed user prompt.
591
+ // Erase the prompt region: a leading blank line + the box. Cursor sits at
592
+ // lastCursorRow inside the box; move up to the box's top border, erase the
593
+ // box rows, then erase the leading blank line, leaving the cursor on that
594
+ // blank row. The caller's single leading newline then yields exactly ONE
595
+ // blank row before the echoed user prompt / message.
596
+ const clearBox = () => {
591
597
  process.stdout.write(`\x1b[${lastCursorRow}A`);
592
598
  for (let i = 0; i < lastTotalRows; i++) {
593
599
  process.stdout.write("\r\x1b[K");
@@ -596,9 +602,19 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
596
602
  }
597
603
  if (lastTotalRows > 1)
598
604
  process.stdout.write(`\x1b[${lastTotalRows - 1}A`);
599
- // Clear the leading blank line above the box.
600
605
  process.stdout.write("\x1b[1A\r\x1b[K");
601
- resolve(resolveVal);
606
+ };
607
+ const settle = (value, isErr) => {
608
+ if (done)
609
+ return;
610
+ done = true;
611
+ process.stdin.setRawMode(false);
612
+ showCursor();
613
+ clearBox();
614
+ if (isErr)
615
+ reject(value);
616
+ else
617
+ resolve(value);
602
618
  };
603
619
  const repaint = (isFirst = false) => {
604
620
  const { frame, cursorRow, cursorCol, totalRows } = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor);
@@ -606,8 +622,11 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
606
622
  lastTotalRows = totalRows;
607
623
  const frameLines = frame.split("\n");
608
624
  if (isFirst) {
609
- // A blank line above the box (mirrors the original's leading Text("")).
625
+ // Draw a leading blank line above the box (mirrors the original's
626
+ // Group(Text(""), panel)): move down one line, clear it as a blank, then
627
+ // move down again so the box starts below that blank.
610
628
  process.stdout.write("\n");
629
+ process.stdout.write("\r\x1b[K\n");
611
630
  }
612
631
  else {
613
632
  // Cursor currently sits at cursorRow (inside the box). Move to the top
@@ -686,18 +705,16 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
686
705
  };
687
706
  const onKeypress = (str, key) => {
688
707
  if (key && key.ctrl && key.name === "c") {
689
- finish(["", []]);
690
- reject(new Error("interrupt"));
708
+ settle(new Error("interrupt"), true);
691
709
  return;
692
710
  }
693
711
  if (key && key.ctrl && (key.name === "d" || key.name === "z")) {
694
- finish([buffer, []]);
695
- reject(new Error("eof"));
712
+ settle(new Error("eof"), true);
696
713
  return;
697
714
  }
698
715
  if (key && key.name === "return") {
699
716
  if (buffer.trim()) {
700
- finish([buffer, pasteSpans]);
717
+ settle([buffer, pasteSpans], false);
701
718
  }
702
719
  return;
703
720
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },