@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 +5 -2
- package/dist/config.js +4 -2
- package/dist/engine.js +3 -1
- package/dist/ui.js +108 -24
- package/package.json +1 -1
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
361
|
-
//
|
|
362
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
391
|
-
//
|
|
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 +
|
|
456
|
+
const cursorCol = col + prefix.length + 4;
|
|
394
457
|
const totalRows = body.length + 2;
|
|
395
|
-
|
|
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
|
-
|
|
434
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|