@oxecli/oxe 1.0.0 → 1.0.2

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
@@ -17,7 +17,7 @@ export class CLI {
17
17
  promptHistory = [];
18
18
  constructor() {
19
19
  clearScreen();
20
- renderPanel("AI CODING AGENT", "Engine: Ready");
20
+ renderPanel("AI CODING AGENT", "", "Engine: Ready");
21
21
  process.stdout.write("\n");
22
22
  this.config = null;
23
23
  }
@@ -189,7 +189,7 @@ export class CLI {
189
189
  this.sessionId = sid;
190
190
  engine.reasoningEffort = String(rec["reasoning_effort"] ?? default_reasoning_effort);
191
191
  clearScreen();
192
- renderPanel(`AI CODING AGENT (resumed ${sid})`, "Engine: Ready");
192
+ renderPanel(`AI CODING AGENT (resumed ${sid})`, "", "Engine: Ready");
193
193
  process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
194
194
  process.stdout.write(`\x1b[2m(${this.inputItems.length} items · ${estimateTokens(this.inputItems).toLocaleString()} tokens · Effort: \x1b[1;36m${engine.reasoningEffort}\x1b[0m\x1b[2m)\x1b[0m\n\n`);
195
195
  renderPanel("Conversation history", "Restored");
@@ -226,7 +226,7 @@ export class CLI {
226
226
  this.story = [];
227
227
  this.sessionId = null;
228
228
  clearScreen();
229
- renderPanel("AI CODING AGENT", "Engine: Ready");
229
+ renderPanel("AI CODING AGENT", "", "Engine: Ready");
230
230
  process.stdout.write("\n");
231
231
  continue;
232
232
  }
@@ -271,7 +271,17 @@ export class CLI {
271
271
  export async function main() {
272
272
  enableAnsi();
273
273
  const app = new CLI();
274
- const config = await loadOrPrompt();
274
+ let config;
275
+ try {
276
+ config = await loadOrPrompt();
277
+ }
278
+ catch (err) {
279
+ if (err?.message === "interrupt" || err?.message === "eof") {
280
+ process.stdout.write("\nClosing terminal session. Goodbye!\n");
281
+ return;
282
+ }
283
+ throw err;
284
+ }
275
285
  app.config = config;
276
286
  const engine = new InferenceEngine(config);
277
287
  try {
package/dist/oxe.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import { main } from "./cli.js";
2
2
  export { main };
3
3
  main().catch((err) => {
4
+ const msg = err?.message;
5
+ if (msg === "interrupt" || msg === "eof") {
6
+ // Graceful exit on Ctrl+C / Ctrl+D — no stack trace.
7
+ process.exit(0);
8
+ }
4
9
  console.error(err);
5
10
  process.exit(1);
6
11
  });
package/dist/ui.js CHANGED
@@ -134,26 +134,47 @@ export function mutedMarkdown(text) {
134
134
  // ---------------------------------------------------------------------------
135
135
  // Panel
136
136
  // ---------------------------------------------------------------------------
137
- export function renderPanel(content, title = "") {
137
+ /**
138
+ * Render a rich-style rounded panel: title in the top border, optional subtitle
139
+ * in the bottom border, content lines inside.
140
+ */
141
+ export function renderPanel(content, title = "", subtitle = "", borderStyle = "90" // 90 = bright black (grey50)
142
+ ) {
138
143
  const w = terminalWidth();
139
144
  const innerW = Math.max(w - 4, 10);
140
145
  const styled = markupToAnsi(content);
146
+ const styledLines = styled.split("\n");
141
147
  const plainLines = styled.replace(/\x1b\[[0-9;]*m/g, "").split("\n");
142
- const border = "─".repeat(innerW);
143
- console.log(`\x1b[90m+${border}+`);
148
+ // Effective width used by the border (accounting for a title/subtitle side)
149
+ const borderW = innerW;
150
+ const topSegs = [];
151
+ topSegs.push("╭");
144
152
  if (title) {
145
- const titleStr = ` ${title} `;
146
- const pad = Math.max(innerW - titleStr.length, 0);
147
- console.log(`\x1b[90m|${titleStr}${" ".repeat(pad)}|`);
153
+ const t = ` ${title} `;
154
+ topSegs.push(`─${t}${"─".repeat(Math.max(borderW - t.length - 2, 0))}`);
148
155
  }
149
- const styledLines = styled.split("\n");
156
+ else {
157
+ topSegs.push("─".repeat(borderW));
158
+ }
159
+ topSegs.push("╮");
160
+ process.stdout.write(`\x1b[${borderStyle}m${topSegs.join("")}\x1b[0m\n`);
150
161
  for (let i = 0; i < styledLines.length; i++) {
151
162
  const line = styledLines[i];
152
163
  const plain = plainLines[i] ?? "";
153
- const pad = Math.max(innerW - plain.length, 0);
154
- console.log(`\x1b[90m| ${line}${" ".repeat(pad)} |\x1b[0m`);
164
+ const pad = Math.max(borderW - plain.length - 1, 0);
165
+ process.stdout.write(`\x1b[${borderStyle}m│\x1b[0m ${line}${" ".repeat(pad)} \x1b[${borderStyle}m│\x1b[0m\n`);
166
+ }
167
+ const bottomSegs = [];
168
+ bottomSegs.push("╰");
169
+ if (subtitle) {
170
+ const s = ` ${subtitle} `;
171
+ bottomSegs.push(`─${s}${"─".repeat(Math.max(borderW - s.length - 2, 0))}`);
155
172
  }
156
- console.log(`\x1b[90m+${border}+\x1b[0m`);
173
+ else {
174
+ bottomSegs.push("─".repeat(borderW));
175
+ }
176
+ bottomSegs.push("╯");
177
+ process.stdout.write(`\x1b[${borderStyle}m${bottomSegs.join("")}\x1b[0m\n`);
157
178
  }
158
179
  // ---------------------------------------------------------------------------
159
180
  // Durations
@@ -324,7 +345,10 @@ export function splitBlocks(buffer, pasteSpans) {
324
345
  }
325
346
  return segs;
326
347
  }
327
- function renderBufferWithCursor(buffer, pasteSpans, prefix, cursor) {
348
+ function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor) {
349
+ const w = terminalWidth();
350
+ const innerW = Math.max(w - 4, 10);
351
+ const border = "─".repeat(innerW);
328
352
  const [row, col] = cursorLineCol(buffer, cursor);
329
353
  const segs = splitBlocks(buffer, pasteSpans);
330
354
  let display = "";
@@ -334,23 +358,39 @@ function renderBufferWithCursor(buffer, pasteSpans, prefix, cursor) {
334
358
  display += " ";
335
359
  display += kind === "collapsed" ? `\x1b[1m\x1b[36m${disp}\x1b[0m` : disp;
336
360
  }
337
- const lines = display.split("\n");
338
361
  const prefixStr = `\x1b[1m${prefix}\x1b[0m `;
339
- // Reconstruct lines, prepending prefix to first line
340
- const rendered = [prefixStr + (lines[0] ?? "")];
341
- for (let i = 1; i < lines.length; i++)
342
- rendered.push(lines[i] ?? "");
343
- // Place cursor on the correct line/col (prefix+1 offset on line 0)
362
+ const innerLines = (prefixStr + display).split("\n");
363
+ // Build the panel lines, each padded to innerW.
364
+ const lines = [];
365
+ for (const l of innerLines) {
366
+ const plain = l.replace(/\x1b\[[0-9;]*m/g, "");
367
+ const pad = Math.max(innerW - 1 - plain.length, 0);
368
+ lines.push(`\x1b[90m│\x1b[0m ${l}${" ".repeat(pad)} \x1b[90m│\x1b[0m`);
369
+ }
370
+ // Placeholder row when empty
371
+ if (!buffer) {
372
+ const ph = `\x1b[2m${PROMPT_PLACEHOLDER}\x1b[0m`;
373
+ const pad = Math.max(innerW - 1 - PROMPT_PLACEHOLDER.length, 0);
374
+ lines.push(`\x1b[90m│\x1b[0m ${ph}${" ".repeat(pad)} \x1b[90m│\x1b[0m`);
375
+ }
376
+ const frame = [
377
+ `\x1b[90m┌─${label}─${border.slice(label.length + 2)}┐\x1b[0m`,
378
+ ...lines,
379
+ `\x1b[90m└${border}┘\x1b[0m`,
380
+ ].join("\n");
381
+ // Compute where the cursor should be placed.
344
382
  let cursorRow = row;
345
383
  let cursorCol = col + (row === 0 ? prefix.length + 1 : 0);
346
- const result = rendered.join("\n");
347
- const currentLines = result.split("\n").length;
348
- // Move cursor up currentLines-1 then right cursorCol
384
+ // +1 row for the top border.
385
+ cursorRow += 1;
386
+ const totalRows = frame.split("\n").length;
349
387
  let cmd = "";
350
- if (currentLines > 1)
351
- cmd += `\x1b[${currentLines - 1}A`;
352
- cmd += `\r\x1b[${cursorCol}C`;
353
- return result + cmd;
388
+ if (totalRows - 1 > cursorRow)
389
+ cmd += `\x1b[${totalRows - 1 - cursorRow}A`;
390
+ else if (cursorRow > totalRows - 1)
391
+ cmd += `\x1b[${cursorRow - (totalRows - 1)}B`;
392
+ cmd += `\r\x1b[${cursorCol + 2}C`;
393
+ return frame + cmd;
354
394
  }
355
395
  export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
356
396
  return new Promise((resolve, reject) => {
@@ -387,7 +427,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
387
427
  resolve(resolveVal);
388
428
  };
389
429
  const repaint = () => {
390
- const frame = renderBufferWithCursor(buffer, pasteSpans, prefix, cursor);
430
+ const frame = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor);
391
431
  process.stdout.write("\r\x1b[K");
392
432
  process.stdout.write(frame);
393
433
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },