@oxecli/oxe 1.0.15 → 1.0.16
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/config.js +5 -1
- package/dist/ui.js +131 -28
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -145,7 +145,7 @@ export const SYSTEM_PROMPT_BODY = "You are a terminal-based coding agent named O
|
|
|
145
145
|
"consequence in your response.\n";
|
|
146
146
|
export const API_KEY_MAX = 64;
|
|
147
147
|
import { validateOxeApiKey } from "./api.js";
|
|
148
|
-
import { renderPanel, markupToAnsi } from "./ui.js";
|
|
148
|
+
import { renderPanel, markupToAnsi, hideCursor, showCursor } from "./ui.js";
|
|
149
149
|
export async function loadOrPrompt() {
|
|
150
150
|
let api_key = "";
|
|
151
151
|
let key_data = {};
|
|
@@ -164,9 +164,13 @@ export async function loadOrPrompt() {
|
|
|
164
164
|
}
|
|
165
165
|
process.stdout.write("\n");
|
|
166
166
|
const authSpinner = new Spinner();
|
|
167
|
+
// promptApiKey echoes input with the cursor visible; hide it while the
|
|
168
|
+
// validating-key label runs so no stray block cursor sits beside it.
|
|
169
|
+
hideCursor();
|
|
167
170
|
authSpinner.start("Authenticating key with Oxe Cloud…");
|
|
168
171
|
const validation = await validateOxeApiKey(api_key);
|
|
169
172
|
authSpinner.stop();
|
|
173
|
+
showCursor();
|
|
170
174
|
if (validation.valid) {
|
|
171
175
|
key_data = validation.key_data || {};
|
|
172
176
|
process.stdout.write(markupToAnsi(`[green]✓ Oxe API Key verified[/green] [dim](${String(key_data["name"] ?? "Desktop")})[/dim]`) + "\n");
|
package/dist/ui.js
CHANGED
|
@@ -456,15 +456,76 @@ export function splitBlocks(buffer, pasteSpans) {
|
|
|
456
456
|
}
|
|
457
457
|
return segs;
|
|
458
458
|
}
|
|
459
|
+
const PROMPT_CARET = "\u0000"; // sentinel marking the caret position
|
|
460
|
+
/**
|
|
461
|
+
* Wrap styled runs into physical rows of `width` visible columns, wrapping long
|
|
462
|
+
* lines so the panel box stays rectangular (mirrors rich's Panel auto-wrap).
|
|
463
|
+
* Returns the styled rows (no borders) and the caret's (row, col) in content
|
|
464
|
+
* coordinates, or (-1,-1) if no caret sentinel is present.
|
|
465
|
+
*/
|
|
466
|
+
function wrapRuns(runs, width) {
|
|
467
|
+
const rows = [];
|
|
468
|
+
let cur = "";
|
|
469
|
+
let curLen = 0;
|
|
470
|
+
let caretRow = -1;
|
|
471
|
+
let caretCol = -1;
|
|
472
|
+
const flush = () => {
|
|
473
|
+
rows.push(cur);
|
|
474
|
+
cur = "";
|
|
475
|
+
curLen = 0;
|
|
476
|
+
};
|
|
477
|
+
for (const run of runs) {
|
|
478
|
+
if (run.text === PROMPT_CARET) {
|
|
479
|
+
// Caret occupies one visible column; if the current row is full, move it
|
|
480
|
+
// to the start of the next row so it stays inside the box.
|
|
481
|
+
if (curLen >= width)
|
|
482
|
+
flush();
|
|
483
|
+
caretRow = rows.length;
|
|
484
|
+
caretCol = curLen;
|
|
485
|
+
cur += run.style + "▏" + "\x1b[0m";
|
|
486
|
+
curLen += 1;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
let text = run.text;
|
|
490
|
+
while (text) {
|
|
491
|
+
// Split off the leading run up to the next newline (if any).
|
|
492
|
+
const nl = text.indexOf("\n");
|
|
493
|
+
const seg = nl === -1 ? text : text.slice(0, nl);
|
|
494
|
+
let rest = seg;
|
|
495
|
+
while (rest.length > 0) {
|
|
496
|
+
if (curLen >= width)
|
|
497
|
+
flush();
|
|
498
|
+
const take = Math.min(rest.length, width - curLen);
|
|
499
|
+
const chunk = rest.slice(0, take);
|
|
500
|
+
cur += run.style ? run.style + chunk + "\x1b[0m" : chunk;
|
|
501
|
+
curLen += take;
|
|
502
|
+
rest = rest.slice(take);
|
|
503
|
+
}
|
|
504
|
+
if (nl !== -1) {
|
|
505
|
+
flush();
|
|
506
|
+
text = text.slice(nl + 1);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
flush();
|
|
514
|
+
return { rows, caretRow, caretCol };
|
|
515
|
+
}
|
|
459
516
|
export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor) {
|
|
460
517
|
const w = terminalWidth();
|
|
461
518
|
const borderW = Math.max(w - 4, 10);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
//
|
|
465
|
-
|
|
519
|
+
// Content width inside the box (between `│ ` and ` │`).
|
|
520
|
+
const innerW = borderW - 2;
|
|
521
|
+
// Build styled runs: prefix, then the text with the `▏` block caret always
|
|
522
|
+
// drawn at the cursor position (mirrors the original rich frame).
|
|
523
|
+
const runs = [];
|
|
524
|
+
runs.push({ text: prefix, style: "\x1b[1m" });
|
|
525
|
+
runs.push({ text: " ", style: "" });
|
|
466
526
|
if (!buffer) {
|
|
467
|
-
|
|
527
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[1m" });
|
|
528
|
+
runs.push({ text: PROMPT_PLACEHOLDER, style: "\x1b[2m" });
|
|
468
529
|
}
|
|
469
530
|
else {
|
|
470
531
|
const segs = splitBlocks(buffer, pasteSpans);
|
|
@@ -513,50 +574,49 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
513
574
|
inside = ranges.length ? ranges[ranges.length - 1][1] - ranges[ranges.length - 1][0] : 0;
|
|
514
575
|
}
|
|
515
576
|
}
|
|
516
|
-
display = "";
|
|
517
577
|
for (let i = 0; i < segs.length; i++) {
|
|
518
578
|
const [, kind, disp] = segs[i];
|
|
519
579
|
if (i && !endsWithWs(segs[i - 1][2]))
|
|
520
|
-
|
|
580
|
+
runs.push({ text: " ", style: "" });
|
|
521
581
|
if (i === target) {
|
|
522
582
|
if (kind === "collapsed") {
|
|
523
|
-
|
|
583
|
+
runs.push({ text: disp, style: "\x1b[1m\x1b[36m" });
|
|
524
584
|
if (!endsWithWs(disp))
|
|
525
|
-
|
|
526
|
-
|
|
585
|
+
runs.push({ text: " ", style: "" });
|
|
586
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[1m" });
|
|
527
587
|
}
|
|
528
588
|
else {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
589
|
+
const before = disp.slice(0, inside);
|
|
590
|
+
const after = disp.slice(inside);
|
|
591
|
+
if (before)
|
|
592
|
+
runs.push({ text: before, style: "" });
|
|
593
|
+
runs.push({ text: PROMPT_CARET, style: "\x1b[1m" });
|
|
594
|
+
if (after)
|
|
595
|
+
runs.push({ text: after, style: "" });
|
|
533
596
|
}
|
|
534
597
|
}
|
|
535
598
|
else {
|
|
536
|
-
|
|
599
|
+
runs.push({ text: disp, style: kind === "collapsed" ? "\x1b[1m\x1b[36m" : "" });
|
|
537
600
|
}
|
|
538
601
|
}
|
|
539
602
|
}
|
|
540
|
-
const
|
|
541
|
-
const lines = content.split("\n");
|
|
603
|
+
const { rows, caretRow, caretCol } = wrapRuns(runs, innerW);
|
|
542
604
|
// Top border with the label embedded on the left.
|
|
543
605
|
const topPad = Math.max(borderW - label.length - 2, 0);
|
|
544
606
|
const top = `\x1b[90m╭─ ${label} ${"─".repeat(topPad)}╮\x1b[0m`;
|
|
545
607
|
// Body rows.
|
|
546
608
|
const body = [];
|
|
547
|
-
for (const l of
|
|
609
|
+
for (const l of rows) {
|
|
548
610
|
const plain = l.replace(/\x1b\[[0-9;]*m/g, "");
|
|
549
611
|
const pad = Math.max(borderW - 2 - plain.length, 0);
|
|
550
612
|
body.push(`\x1b[90m│\x1b[0m ${l}${" ".repeat(pad)} \x1b[90m│\x1b[0m`);
|
|
551
613
|
}
|
|
552
614
|
const bottom = `\x1b[90m╰${"─".repeat(borderW)}╯\x1b[0m`;
|
|
553
615
|
const frame = [top, ...body, bottom].join("\n");
|
|
554
|
-
// Cursor placement: one row below the top border.
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const cursorRow = row + 1;
|
|
559
|
-
const cursorCol = col + prefix.length + 4;
|
|
616
|
+
// Cursor placement: one row below the top border. The body prefix (`│ `)
|
|
617
|
+
// shifts the content right by 2 columns, so add 2 to the wrapped caret col.
|
|
618
|
+
const cursorRow = (caretRow === -1 ? 0 : caretRow) + 1;
|
|
619
|
+
const cursorCol = (caretCol === -1 ? 0 : caretCol) + 2;
|
|
560
620
|
const totalRows = body.length + 2;
|
|
561
621
|
return { frame, cursorRow, cursorCol, totalRows };
|
|
562
622
|
}
|
|
@@ -735,15 +795,32 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
735
795
|
pasteSpans = adjusted.filter(([s, e]) => s < e);
|
|
736
796
|
};
|
|
737
797
|
const moveLeft = () => {
|
|
738
|
-
if (cursor
|
|
739
|
-
|
|
798
|
+
if (cursor <= 0)
|
|
799
|
+
return;
|
|
800
|
+
// Jump the caret to the start of a paste span it sits just past (mirrors
|
|
801
|
+
// Python), so a collapsed block is treated as one unit.
|
|
802
|
+
for (const [s, e] of pasteSpans) {
|
|
803
|
+
if (s < cursor && cursor <= e) {
|
|
804
|
+
cursor = s;
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
cursor -= 1;
|
|
740
809
|
};
|
|
741
810
|
const moveRight = () => {
|
|
742
|
-
if (cursor
|
|
743
|
-
|
|
811
|
+
if (cursor >= buffer.length)
|
|
812
|
+
return;
|
|
813
|
+
for (const [s, e] of pasteSpans) {
|
|
814
|
+
if (s <= cursor && cursor < e) {
|
|
815
|
+
cursor = e;
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
cursor += 1;
|
|
744
820
|
};
|
|
745
821
|
const moveUp = () => {
|
|
746
822
|
if (!buffer.includes("\n")) {
|
|
823
|
+
// Single row: navigate command history.
|
|
747
824
|
if (!hist.length)
|
|
748
825
|
return;
|
|
749
826
|
if (histIdx === hist.length) {
|
|
@@ -755,10 +832,21 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
755
832
|
cursor = buffer.length;
|
|
756
833
|
pasteSpans = [];
|
|
757
834
|
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
// Multi row: move the caret up to the previous line, preserving column.
|
|
838
|
+
if (cursor > 0) {
|
|
839
|
+
let lineStart = buffer.lastIndexOf("\n", cursor - 1);
|
|
840
|
+
lineStart = lineStart !== -1 ? lineStart : 0;
|
|
841
|
+
let prevStart = buffer.lastIndexOf("\n", lineStart - 1);
|
|
842
|
+
prevStart = prevStart !== -1 ? prevStart : 0;
|
|
843
|
+
const [, col] = cursorLineCol(buffer, cursor);
|
|
844
|
+
cursor = prevStart + Math.min(col, lineStart - prevStart);
|
|
758
845
|
}
|
|
759
846
|
};
|
|
760
847
|
const moveDown = () => {
|
|
761
848
|
if (!buffer.includes("\n")) {
|
|
849
|
+
// Single row: navigate command history.
|
|
762
850
|
if (histIdx === hist.length || !draft)
|
|
763
851
|
return;
|
|
764
852
|
histIdx += 1;
|
|
@@ -772,6 +860,21 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
|
|
|
772
860
|
cursor = buffer.length;
|
|
773
861
|
pasteSpans = [];
|
|
774
862
|
}
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
// Multi row: move the caret down to the next line, preserving column.
|
|
866
|
+
if (cursor < buffer.length) {
|
|
867
|
+
const [, col] = cursorLineCol(buffer, cursor);
|
|
868
|
+
let nextStart = buffer.indexOf("\n", cursor);
|
|
869
|
+
if (nextStart === -1) {
|
|
870
|
+
cursor = buffer.length;
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
nextStart += 1;
|
|
874
|
+
let nxtEnd = buffer.indexOf("\n", nextStart);
|
|
875
|
+
nxtEnd = nxtEnd !== -1 ? nxtEnd : buffer.length;
|
|
876
|
+
cursor = Math.min(nextStart + col, nxtEnd);
|
|
877
|
+
}
|
|
775
878
|
}
|
|
776
879
|
};
|
|
777
880
|
const onKeypress = (str, key) => {
|