@oxecli/oxe 1.0.3 → 1.0.4

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", false);
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", false);
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", false);
230
230
  process.stdout.write("\n");
231
231
  continue;
232
232
  }
package/dist/config.js CHANGED
@@ -153,7 +153,7 @@ export async function loadOrPrompt() {
153
153
  renderPanel("[bold white]Oxe Desktop Authentication[/bold white]\n\n" +
154
154
  `[dim]Only valid Oxe API keys (starting with [bold]${OXE_KEY_PREFIX}[/bold]) generated on your\n` +
155
155
  "Oxe web dashboard are authorized to run this agent.\n\n" +
156
- "Get your key at: [bold underline]http://localhost:5173/dashboard/api-keys[/bold underline][/dim]", "Oxe Cloud Access");
156
+ "Get your key at: [bold underline]http://localhost:5173/dashboard/api-keys[/bold underline][/dim]", "Oxe Cloud Access", "", false);
157
157
  process.stdout.write("\n");
158
158
  api_key = await promptApiKey("Enter Oxe API Key: ");
159
159
  if (!api_key) {
package/dist/ui.js CHANGED
@@ -135,46 +135,54 @@ export function mutedMarkdown(text) {
135
135
  // Panel
136
136
  // ---------------------------------------------------------------------------
137
137
  /**
138
- * Render a rich-style rounded panel: title in the top border, optional subtitle
139
- * in the bottom border, content lines inside.
138
+ * Render a rich-style rounded panel.
139
+ *
140
+ * Matches rich's Panel behavior:
141
+ * - `expand=false` shrinks the frame to fit the content (banner/auth).
142
+ * - title/subtitle are embedded in the top/bottom borders, centered by
143
+ * default (rich default) or left-aligned.
140
144
  */
141
- export function renderPanel(content, title = "", subtitle = "", borderStyle = "90" // 90 = bright black (grey50)
142
- ) {
143
- const w = terminalWidth();
144
- const innerW = Math.max(w - 4, 10);
145
+ export function renderPanel(content, title = "", subtitle = "", expand = true, borderStyle = "90", // 90 = bright black (grey50)
146
+ titleAlign = "center") {
145
147
  const styled = markupToAnsi(content);
146
148
  const styledLines = styled.split("\n");
147
149
  const plainLines = styled.replace(/\x1b\[[0-9;]*m/g, "").split("\n");
148
- // Effective width used by the border (accounting for a title/subtitle side)
149
- const borderW = innerW;
150
- const topSegs = [];
151
- topSegs.push("╭");
152
- if (title) {
153
- const t = ` ${title} `;
154
- topSegs.push(`─${t}${"─".repeat(Math.max(borderW - t.length - 2, 0))}`);
155
- }
156
- else {
157
- topSegs.push("─".repeat(borderW));
158
- }
159
- topSegs.push("╮");
160
- process.stdout.write(`\x1b[${borderStyle}m${topSegs.join("")}\x1b[0m\n`);
150
+ // Inner width = widest content line + 1 space padding each side.
151
+ let contentW = 0;
152
+ for (const p of plainLines)
153
+ contentW = Math.max(contentW, p.length);
154
+ contentW = Math.max(contentW, 1);
155
+ let innerW = contentW + 2;
156
+ if (title)
157
+ innerW = Math.max(innerW, title.length + 2);
158
+ if (subtitle)
159
+ innerW = Math.max(innerW, subtitle.length + 2);
160
+ const w = terminalWidth();
161
+ innerW = expand ? Math.max(w - 4, 10) : Math.min(innerW, w - 4);
162
+ // Build a border run of `innerW` chars with `text` embedded.
163
+ const embed = (text, align) => {
164
+ if (!text)
165
+ return "─".repeat(innerW);
166
+ const inner = ` ${text} `;
167
+ const fill = Math.max(innerW - inner.length, 0);
168
+ if (align === "left") {
169
+ return `${inner}${"─".repeat(fill)}`;
170
+ }
171
+ const left = Math.floor(fill / 2);
172
+ const right = fill - left;
173
+ return `${"─".repeat(left)}${inner}${"─".repeat(right)}`;
174
+ };
175
+ const top = `╭${embed(title, titleAlign)}╮`;
176
+ process.stdout.write(`\x1b[${borderStyle}m${top}\x1b[0m\n`);
161
177
  for (let i = 0; i < styledLines.length; i++) {
162
178
  const line = styledLines[i];
163
179
  const plain = plainLines[i] ?? "";
164
- const pad = Math.max(borderW - plain.length - 1, 0);
180
+ // Keep exactly one space padding each side; only pad the right to fill.
181
+ const pad = Math.max(innerW - plain.length - 2, 0);
165
182
  process.stdout.write(`\x1b[${borderStyle}m│\x1b[0m ${line}${" ".repeat(pad)} \x1b[${borderStyle}m│\x1b[0m\n`);
166
183
  }
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))}`);
172
- }
173
- else {
174
- bottomSegs.push("─".repeat(borderW));
175
- }
176
- bottomSegs.push("╯");
177
- process.stdout.write(`\x1b[${borderStyle}m${bottomSegs.join("")}\x1b[0m\n`);
184
+ const bottom = `╰${embed(subtitle, "center")}╯`;
185
+ process.stdout.write(`\x1b[${borderStyle}m${bottom}\x1b[0m\n`);
178
186
  }
179
187
  // ---------------------------------------------------------------------------
180
188
  // Durations
@@ -345,51 +353,51 @@ export function splitBlocks(buffer, pasteSpans) {
345
353
  }
346
354
  return segs;
347
355
  }
348
- function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor) {
356
+ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor) {
349
357
  const w = terminalWidth();
350
- const innerW = Math.max(w - 4, 10);
351
- const border = "─".repeat(innerW);
358
+ const borderW = Math.max(w - 4, 10);
352
359
  const [row, col] = cursorLineCol(buffer, cursor);
353
- const segs = splitBlocks(buffer, pasteSpans);
354
- let display = "";
355
- for (let i = 0; i < segs.length; i++) {
356
- const [, kind, disp] = segs[i];
357
- if (i && !endsWithWs(segs[i - 1][2]))
358
- display += " ";
359
- display += kind === "collapsed" ? `\x1b[1m\x1b[36m${disp}\x1b[0m` : disp;
360
+ // Build the inner content line(s) — the whole prefix+text (or placeholder)
361
+ // lives on a single row when the buffer is empty, matching the original.
362
+ let content;
363
+ if (!buffer) {
364
+ content = `\x1b[1m${prefix}\x1b[0m \x1b[1m▏\x1b[0m\x1b[2m${PROMPT_PLACEHOLDER}\x1b[0m`;
360
365
  }
361
- const prefixStr = `\x1b[1m${prefix}\x1b[0m `;
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`);
366
+ else {
367
+ const segs = splitBlocks(buffer, pasteSpans);
368
+ let display = "";
369
+ for (let i = 0; i < segs.length; i++) {
370
+ const [, kind, disp] = segs[i];
371
+ if (i && !endsWithWs(segs[i - 1][2]))
372
+ display += " ";
373
+ display += kind === "collapsed" ? `\x1b[1m\x1b[36m${disp}\x1b[0m` : disp;
374
+ }
375
+ content = `\x1b[1m${prefix}\x1b[0m ${display}`;
369
376
  }
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`);
377
+ const lines = content.split("\n");
378
+ // Top border with the label embedded on the left.
379
+ const topPad = Math.max(borderW - label.length - 2, 0);
380
+ const top = `\x1b[90m╭─ ${label} ${"─".repeat(topPad)}╮\x1b[0m`;
381
+ // Body rows.
382
+ const body = [];
383
+ for (const l of lines) {
384
+ const plain = l.replace(/\x1b\[[0-9;]*m/g, "");
385
+ const pad = Math.max(borderW - 2 - plain.length, 0);
386
+ body.push(`\x1b[90m│\x1b[0m ${l}${" ".repeat(pad)} \x1b[90m│\x1b[0m`);
375
387
  }
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.
382
- let cursorRow = row;
383
- let cursorCol = col + (row === 0 ? prefix.length + 1 : 0);
384
- // +1 row for the top border.
385
- cursorRow += 1;
386
- const totalRows = frame.split("\n").length;
388
+ const bottom = `\x1b[90m╰${"─".repeat(borderW)}╯\x1b[0m`;
389
+ const frame = [top, ...body, bottom].join("\n");
390
+ // Cursor placement: one row below the top border. Column includes the
391
+ // "│ " left border (2) plus "prefix " (prefix.length + 1).
392
+ const cursorRow = row + 1;
393
+ const cursorCol = col + prefix.length + 3;
394
+ const totalRows = body.length + 2;
387
395
  let cmd = "";
388
396
  if (totalRows - 1 > cursorRow)
389
397
  cmd += `\x1b[${totalRows - 1 - cursorRow}A`;
390
398
  else if (cursorRow > totalRows - 1)
391
399
  cmd += `\x1b[${cursorRow - (totalRows - 1)}B`;
392
- cmd += `\r\x1b[${cursorCol + 2}C`;
400
+ cmd += `\r\x1b[${cursorCol}C`;
393
401
  return frame + cmd;
394
402
  }
395
403
  export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },