@oxecli/oxe 1.0.4 → 1.0.6

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
@@ -189,9 +189,12 @@ 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", false);
192
+ renderPanel("AI CODING AGENT", "", `Engine: Ready ${sid}`, false);
193
+ process.stdout.write("\n");
193
194
  process.stdout.write(`\x1b[2mResumed:\x1b[0m ${truncateLabel(rec["label"])}\n`);
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
+ process.stdout.write("\n");
196
+ 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`);
197
+ process.stdout.write("\n");
195
198
  renderPanel("Conversation history", "Restored");
196
199
  process.stdout.write("\n");
197
200
  if (this.story.length)
package/dist/config.js CHANGED
@@ -166,11 +166,13 @@ export async function loadOrPrompt() {
166
166
  const validation = await validateOxeApiKey(api_key);
167
167
  if (validation.valid) {
168
168
  key_data = validation.key_data || {};
169
+ process.stdout.write("\n");
169
170
  process.stdout.write(markupToAnsi(`[green]✓ Oxe API Key verified[/green] [dim](${String(key_data["name"] ?? "Desktop")})[/dim]`) + "\n");
170
171
  process.env.OXE_API_KEY = api_key;
171
172
  break;
172
173
  }
173
174
  else {
175
+ process.stdout.write("\n");
174
176
  process.stdout.write(`\x1b[31mAuthentication Error:\x1b[0m ${validation.error}\n`);
175
177
  process.stdout.write("\n");
176
178
  api_key = "";
@@ -209,7 +211,7 @@ export async function loadOrPrompt() {
209
211
  };
210
212
  }
211
213
  /**
212
- * Prompt for a secret (API key) without echoing it to the terminal.
214
+ * Prompt for an API key, echoing what the user types.
213
215
  * Falls back to a plain readline read when stdin isn't a TTY (e.g. piped input).
214
216
  */
215
217
  export async function promptApiKey(prompt) {
@@ -262,7 +264,7 @@ export async function promptApiKey(prompt) {
262
264
  if (b < 32)
263
265
  continue;
264
266
  input += String.fromCharCode(b);
265
- process.stdout.write("*");
267
+ process.stdout.write(String.fromCharCode(b));
266
268
  }
267
269
  };
268
270
  const cleanup = () => {
package/dist/engine.js CHANGED
@@ -76,7 +76,8 @@ export class InferenceEngine {
76
76
  this.client = new OpenAI({
77
77
  apiKey: config["api_key"],
78
78
  baseURL: config["base_url"],
79
- timeout: config["timeout"] ?? 120,
79
+ // OpenAI SDK `timeout` is in MILLISECONDS; config stores seconds.
80
+ timeout: (config["timeout"] ?? 120) * 1000,
80
81
  maxRetries: config["max_retries"] ?? 2,
81
82
  });
82
83
  this.temperature = config["temperature"] ?? null;
@@ -175,6 +176,7 @@ export class InferenceEngine {
175
176
  parallel_tool_calls: true,
176
177
  max_output_tokens,
177
178
  reasoning: { effort: this.reasoningEffort },
179
+ stream: true,
178
180
  stream_options: { include_usage: true },
179
181
  store: true,
180
182
  };
package/dist/ui.js CHANGED
@@ -357,23 +357,84 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
357
357
  const w = terminalWidth();
358
358
  const borderW = Math.max(w - 4, 10);
359
359
  const [row, col] = cursorLineCol(buffer, cursor);
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;
360
+ // Build the inner content: prefix, then the text with the `▏` block cursor
361
+ // always drawn at the cursor position (mirrors the original rich frame).
362
+ let display;
363
363
  if (!buffer) {
364
- content = `\x1b[1m${prefix}\x1b[0m \x1b[1m▏\x1b[0m\x1b[2m${PROMPT_PLACEHOLDER}\x1b[0m`;
364
+ display = `\x1b[1m▏\x1b[0m\x1b[2m${PROMPT_PLACEHOLDER}\x1b[0m`;
365
365
  }
366
366
  else {
367
367
  const segs = splitBlocks(buffer, pasteSpans);
368
- let display = "";
368
+ // Recompute each segment's buffer range [a, b) so we can locate the cursor.
369
+ const ranges = [];
370
+ {
371
+ let pos = 0;
372
+ const pts = new Set([0, buffer.length]);
373
+ for (const [s, e] of pasteSpans) {
374
+ pts.add(s);
375
+ pts.add(e);
376
+ }
377
+ const sorted = [...pts].sort((a, b) => a - b);
378
+ const isPaste = (a, b) => pasteSpans.some(([s, e]) => s <= a && b <= e);
379
+ const used = new Set();
380
+ for (const seg of segs) {
381
+ for (let i = 0; i < sorted.length - 1; i++) {
382
+ const a = sorted[i];
383
+ const b = sorted[i + 1];
384
+ if (a === b || used.has(a))
385
+ continue;
386
+ used.add(a);
387
+ ranges.push([a, b]);
388
+ break;
389
+ }
390
+ }
391
+ }
392
+ // Determine the target segment + inside offset (mirrors Python).
393
+ let target = -1;
394
+ let inside = 0;
395
+ if (cursor >= buffer.length) {
396
+ target = segs.length - 1;
397
+ inside = ranges.length ? ranges[ranges.length - 1][1] - ranges[ranges.length - 1][0] : 0;
398
+ }
399
+ else {
400
+ for (let i = 0; i < ranges.length; i++) {
401
+ const [a, b] = ranges[i];
402
+ if (a <= cursor && cursor <= b) {
403
+ target = i;
404
+ inside = cursor - a;
405
+ break;
406
+ }
407
+ }
408
+ if (target === -1) {
409
+ target = segs.length - 1;
410
+ inside = ranges.length ? ranges[ranges.length - 1][1] - ranges[ranges.length - 1][0] : 0;
411
+ }
412
+ }
413
+ display = "";
369
414
  for (let i = 0; i < segs.length; i++) {
370
415
  const [, kind, disp] = segs[i];
371
416
  if (i && !endsWithWs(segs[i - 1][2]))
372
417
  display += " ";
373
- display += kind === "collapsed" ? `\x1b[1m\x1b[36m${disp}\x1b[0m` : disp;
418
+ if (i === target) {
419
+ if (kind === "collapsed") {
420
+ display += `\x1b[1m\x1b[36m${disp}\x1b[0m`;
421
+ if (!endsWithWs(disp))
422
+ display += " ";
423
+ display += `\x1b[1m▏\x1b[0m`;
424
+ }
425
+ else {
426
+ const bold = i === target;
427
+ display += disp.slice(0, inside);
428
+ display += `\x1b[1m▏\x1b[0m`;
429
+ display += disp.slice(inside);
430
+ }
431
+ }
432
+ else {
433
+ display += kind === "collapsed" ? `\x1b[1m\x1b[36m${disp}\x1b[0m` : disp;
434
+ }
374
435
  }
375
- content = `\x1b[1m${prefix}\x1b[0m ${display}`;
376
436
  }
437
+ const content = `\x1b[1m${prefix}\x1b[0m ${display}`;
377
438
  const lines = content.split("\n");
378
439
  // Top border with the label embedded on the left.
379
440
  const topPad = Math.max(borderW - label.length - 2, 0);
@@ -387,18 +448,14 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
387
448
  }
388
449
  const bottom = `\x1b[90m╰${"─".repeat(borderW)}╯\x1b[0m`;
389
450
  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).
451
+ // Cursor placement: one row below the top border. Preceding columns are
452
+ // `│ ` (2) + prefix (prefix.length) + ` ` (1) = prefix.length+3, so the first
453
+ // display char (the `▏` block cursor) sits at column prefix.length+4 relative
454
+ // to line column `col`.
392
455
  const cursorRow = row + 1;
393
- const cursorCol = col + prefix.length + 3;
456
+ const cursorCol = col + prefix.length + 4;
394
457
  const totalRows = body.length + 2;
395
- let cmd = "";
396
- if (totalRows - 1 > cursorRow)
397
- cmd += `\x1b[${totalRows - 1 - cursorRow}A`;
398
- else if (cursorRow > totalRows - 1)
399
- cmd += `\x1b[${cursorRow - (totalRows - 1)}B`;
400
- cmd += `\r\x1b[${cursorCol}C`;
401
- return frame + cmd;
458
+ return { frame, cursorRow, cursorCol, totalRows };
402
459
  }
403
460
  export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
404
461
  return new Promise((resolve, reject) => {
@@ -425,19 +482,46 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
425
482
  process.stdin.setRawMode(true);
426
483
  process.stdin.resume();
427
484
  let done = false;
485
+ let lastCursorRow = 0;
486
+ let lastTotalRows = 0;
428
487
  const finish = (resolveVal) => {
429
488
  if (done)
430
489
  return;
431
490
  done = true;
432
491
  process.stdin.setRawMode(false);
433
- process.stdout.write("\r\x1b[K");
434
- process.stdout.write("\n");
492
+ // Erase the whole prompt box (which occupies `lastTotalRows` lines) so no
493
+ // frame borders are left behind, then leave the cursor on the box's top
494
+ // row (a blank line). That way the caller's single leading newline yields
495
+ // exactly ONE blank row before the echoed user prompt — never two.
496
+ process.stdout.write(`\x1b[${lastCursorRow}A`);
497
+ for (let i = 0; i < lastTotalRows; i++) {
498
+ process.stdout.write("\r\x1b[K");
499
+ if (i < lastTotalRows - 1)
500
+ process.stdout.write("\n");
501
+ }
502
+ // Cursor is now on the last box row; move back up to the box's top row.
503
+ if (lastTotalRows > 1)
504
+ process.stdout.write(`\x1b[${lastTotalRows - 1}A`);
435
505
  resolve(resolveVal);
436
506
  };
437
- const repaint = () => {
438
- const frame = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor);
439
- process.stdout.write("\r\x1b[K");
440
- process.stdout.write(frame);
507
+ const repaint = (isFirst = false) => {
508
+ const { frame, cursorRow, cursorCol, totalRows } = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor);
509
+ lastCursorRow = cursorRow;
510
+ lastTotalRows = totalRows;
511
+ const frameLines = frame.split("\n");
512
+ if (!isFirst) {
513
+ // Cursor currently sits at cursorRow (inside the box). Move to the top
514
+ // border, then clear+rewrite each line so old content is fully removed.
515
+ process.stdout.write(`\x1b[${cursorRow}A`);
516
+ }
517
+ for (let i = 0; i < frameLines.length; i++) {
518
+ process.stdout.write("\r\x1b[K" + frameLines[i]);
519
+ if (i < frameLines.length - 1)
520
+ process.stdout.write("\n");
521
+ }
522
+ // Reposition the cursor inside the box.
523
+ const fromBottom = totalRows - 1 - cursorRow;
524
+ process.stdout.write(`\x1b[${fromBottom}A\r\x1b[${cursorCol}C`);
441
525
  };
442
526
  const insert = (text) => {
443
527
  if (histIdx !== hist.length) {
@@ -559,7 +643,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
559
643
  }
560
644
  };
561
645
  process.stdin.on("keypress", onKeypress);
562
- repaint();
646
+ repaint(true);
563
647
  });
564
648
  }
565
649
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },