groundcrew-cli 0.15.9 → 0.15.11
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/index.js +56 -12
- package/package.json +1 -1
- package/src/index.ts +84 -25
package/dist/index.js
CHANGED
|
@@ -426,8 +426,14 @@ function setupInlineSuggestions(rl) {
|
|
|
426
426
|
buf.push(`\x1B[${dropdownLines}A`);
|
|
427
427
|
dropdownLines = 0;
|
|
428
428
|
}
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
if (ghostLen > 0) {
|
|
430
|
+
buf.push("\x1B[s");
|
|
431
|
+
buf.push("\x1B[999C");
|
|
432
|
+
buf.push(`\x1B[${ghostLen}D`);
|
|
433
|
+
buf.push("\x1B[K");
|
|
434
|
+
buf.push("\x1B[u");
|
|
435
|
+
ghostLen = 0;
|
|
436
|
+
}
|
|
431
437
|
if (buf.length) process.stdout.write(buf.join(""));
|
|
432
438
|
};
|
|
433
439
|
const showGhost = () => {
|
|
@@ -456,13 +462,14 @@ function setupInlineSuggestions(rl) {
|
|
|
456
462
|
}
|
|
457
463
|
dropdownLines = count;
|
|
458
464
|
buf.push(`\x1B[${count}A`);
|
|
459
|
-
|
|
460
|
-
const inputLine = rl.line || "";
|
|
461
|
-
buf.push(`\r${prompt}${inputLine}`);
|
|
465
|
+
buf.push(`\r`);
|
|
462
466
|
}
|
|
463
467
|
process.stdout.write(buf.join(""));
|
|
464
|
-
if (dropdownLines > 0
|
|
465
|
-
|
|
468
|
+
if (dropdownLines > 0) {
|
|
469
|
+
rl._refreshLine();
|
|
470
|
+
if (remainder) {
|
|
471
|
+
process.stdout.write(`\x1B[K\x1B[2m${remainder}\x1B[0m\x1B[${remainder.length}D`);
|
|
472
|
+
}
|
|
466
473
|
}
|
|
467
474
|
};
|
|
468
475
|
process.stdin.on("keypress", (_ch, key) => {
|
|
@@ -474,18 +481,55 @@ function setupInlineSuggestions(rl) {
|
|
|
474
481
|
});
|
|
475
482
|
}
|
|
476
483
|
async function chat(explicitSession) {
|
|
477
|
-
process.stdout.write("\x1B[>1u");
|
|
484
|
+
process.stdout.write("\x1B[?2004h\x1B[>1u");
|
|
478
485
|
const originalStdinEmit = process.stdin.emit.bind(process.stdin);
|
|
479
|
-
let
|
|
486
|
+
let pasteBuffer = "";
|
|
487
|
+
let isPasting = false;
|
|
480
488
|
process.stdin.emit = function(event, ...args) {
|
|
481
489
|
if (event === "data") {
|
|
482
490
|
const data = args[0];
|
|
483
|
-
|
|
491
|
+
let str = typeof data === "string" ? data : data.toString();
|
|
492
|
+
const pasteStart = str.indexOf("\x1B[200~");
|
|
493
|
+
if (pasteStart !== -1) {
|
|
494
|
+
isPasting = true;
|
|
495
|
+
if (pasteStart > 0) {
|
|
496
|
+
originalStdinEmit(event, Buffer.from(str.slice(0, pasteStart)));
|
|
497
|
+
}
|
|
498
|
+
str = str.slice(pasteStart + 6);
|
|
499
|
+
}
|
|
500
|
+
if (isPasting) {
|
|
501
|
+
const pasteEnd = str.indexOf("\x1B[201~");
|
|
502
|
+
if (pasteEnd !== -1) {
|
|
503
|
+
pasteBuffer += str.slice(0, pasteEnd);
|
|
504
|
+
const afterPaste = str.slice(pasteEnd + 6);
|
|
505
|
+
isPasting = false;
|
|
506
|
+
const pasted = pasteBuffer.replace(/[\r\n]+$/, "");
|
|
507
|
+
pasteBuffer = "";
|
|
508
|
+
if (pasted.includes("\n") || pasted.includes("\r")) {
|
|
509
|
+
const lines = pasted.split(/\r?\n/);
|
|
510
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
511
|
+
originalStdinEmit(event, Buffer.from(lines[i] + "\\\r"));
|
|
512
|
+
}
|
|
513
|
+
originalStdinEmit(event, Buffer.from(lines[lines.length - 1]));
|
|
514
|
+
} else {
|
|
515
|
+
originalStdinEmit(event, Buffer.from(pasted));
|
|
516
|
+
}
|
|
517
|
+
if (afterPaste) {
|
|
518
|
+
return originalStdinEmit(event, Buffer.from(afterPaste));
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
} else {
|
|
522
|
+
pasteBuffer += str;
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
484
526
|
if (str.includes("\x1B[13;2u")) {
|
|
485
|
-
pendingShiftEnter = true;
|
|
486
527
|
const replaced = str.replace(/\x1b\[13;2u/g, "\\\r");
|
|
487
528
|
return originalStdinEmit(event, Buffer.from(replaced));
|
|
488
529
|
}
|
|
530
|
+
if (str === "\x1B\r" || str === "\x1B\n") {
|
|
531
|
+
return originalStdinEmit(event, Buffer.from("\\\r"));
|
|
532
|
+
}
|
|
489
533
|
}
|
|
490
534
|
return originalStdinEmit(event, ...args);
|
|
491
535
|
};
|
|
@@ -543,7 +587,7 @@ async function chat(explicitSession) {
|
|
|
543
587
|
console.log();
|
|
544
588
|
let continuationBuffer = [];
|
|
545
589
|
rl.on("close", () => {
|
|
546
|
-
process.stdout.write("\x1B[<u");
|
|
590
|
+
process.stdout.write("\x1B[?2004l\x1B[<u");
|
|
547
591
|
console.log(dim("\nBye."));
|
|
548
592
|
process.exit(0);
|
|
549
593
|
});
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -559,10 +559,17 @@ function setupInlineSuggestions(rl: readline.Interface): void {
|
|
|
559
559
|
buf.push(`\x1b[${dropdownLines}A`); // back up to prompt line
|
|
560
560
|
dropdownLines = 0;
|
|
561
561
|
}
|
|
562
|
-
// Clear inline ghost
|
|
563
|
-
//
|
|
564
|
-
|
|
565
|
-
ghostLen
|
|
562
|
+
// Clear inline ghost only if one is showing — save cursor, jump to
|
|
563
|
+
// ghost start (far right), erase it, then restore cursor position.
|
|
564
|
+
// This avoids nuking typed text when cursor is mid-line (left arrow).
|
|
565
|
+
if (ghostLen > 0) {
|
|
566
|
+
buf.push("\x1b[s"); // save cursor position
|
|
567
|
+
buf.push("\x1b[999C"); // jump to far right of line
|
|
568
|
+
buf.push(`\x1b[${ghostLen}D`); // back up to ghost start
|
|
569
|
+
buf.push("\x1b[K"); // clear from ghost start to EOL
|
|
570
|
+
buf.push("\x1b[u"); // restore cursor position
|
|
571
|
+
ghostLen = 0;
|
|
572
|
+
}
|
|
566
573
|
if (buf.length) process.stdout.write(buf.join(""));
|
|
567
574
|
};
|
|
568
575
|
|
|
@@ -604,25 +611,28 @@ function setupInlineSuggestions(rl: readline.Interface): void {
|
|
|
604
611
|
}
|
|
605
612
|
dropdownLines = count;
|
|
606
613
|
|
|
607
|
-
//
|
|
614
|
+
// Back to prompt line and restore horizontal position
|
|
615
|
+
// Move up to prompt line
|
|
608
616
|
buf.push(`\x1b[${count}A`);
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
buf.push(`\r${prompt}${inputLine}`);
|
|
617
|
+
// Move to column 1, then rewrite prompt+line to position cursor correctly
|
|
618
|
+
buf.push(`\r`);
|
|
619
|
+
// Let readline handle cursor positioning
|
|
613
620
|
}
|
|
614
621
|
|
|
615
622
|
process.stdout.write(buf.join(""));
|
|
616
|
-
//
|
|
617
|
-
if (dropdownLines > 0
|
|
618
|
-
|
|
623
|
+
// After dropdown, force readline to redraw prompt line to fix cursor position
|
|
624
|
+
if (dropdownLines > 0) {
|
|
625
|
+
(rl as any)._refreshLine();
|
|
626
|
+
// Re-draw inline ghost since _refreshLine cleared it
|
|
627
|
+
if (remainder) {
|
|
628
|
+
process.stdout.write(`\x1b[K\x1b[2m${remainder}\x1b[0m\x1b[${remainder.length}D`);
|
|
629
|
+
}
|
|
619
630
|
}
|
|
620
631
|
};
|
|
621
632
|
|
|
622
633
|
process.stdin.on("keypress", (_ch: string, key: any) => {
|
|
623
634
|
if (!key) return;
|
|
624
635
|
|
|
625
|
-
|
|
626
636
|
clearGhost();
|
|
627
637
|
if (key.name !== "return" && key.name !== "tab") {
|
|
628
638
|
setImmediate(showGhost);
|
|
@@ -631,25 +641,74 @@ function setupInlineSuggestions(rl: readline.Interface): void {
|
|
|
631
641
|
}
|
|
632
642
|
|
|
633
643
|
async function chat(explicitSession?: string): Promise<void> {
|
|
634
|
-
// Enable Kitty keyboard protocol
|
|
635
|
-
|
|
636
|
-
process.stdout.write("\x1b[>1u");
|
|
644
|
+
// Enable bracketed paste mode + Kitty keyboard protocol (Shift+Enter detection)
|
|
645
|
+
process.stdout.write("\x1b[?2004h\x1b[>1u");
|
|
637
646
|
|
|
638
|
-
// Intercept raw stdin
|
|
639
|
-
//
|
|
647
|
+
// Intercept raw stdin for:
|
|
648
|
+
// 1. Bracketed paste (\x1b[200~ ... \x1b[201~) — buffer paste, submit as single task
|
|
649
|
+
// 2. Shift+Enter (\x1b[13;2u via Kitty protocol) — line continuation
|
|
650
|
+
// 3. Alt+Enter (\x1b\r — universal fallback) — line continuation
|
|
640
651
|
const originalStdinEmit = process.stdin.emit.bind(process.stdin);
|
|
641
|
-
let
|
|
652
|
+
let pasteBuffer = "";
|
|
653
|
+
let isPasting = false;
|
|
654
|
+
|
|
642
655
|
process.stdin.emit = function (event: string, ...args: any[]) {
|
|
643
656
|
if (event === "data") {
|
|
644
657
|
const data = args[0] as Buffer | string;
|
|
645
|
-
|
|
658
|
+
let str = typeof data === "string" ? data : data.toString();
|
|
659
|
+
|
|
660
|
+
// --- Bracketed paste handling ---
|
|
661
|
+
const pasteStart = str.indexOf("\x1b[200~");
|
|
662
|
+
if (pasteStart !== -1) {
|
|
663
|
+
isPasting = true;
|
|
664
|
+
if (pasteStart > 0) {
|
|
665
|
+
originalStdinEmit(event, Buffer.from(str.slice(0, pasteStart)));
|
|
666
|
+
}
|
|
667
|
+
str = str.slice(pasteStart + 6); // skip \x1b[200~
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (isPasting) {
|
|
671
|
+
const pasteEnd = str.indexOf("\x1b[201~");
|
|
672
|
+
if (pasteEnd !== -1) {
|
|
673
|
+
pasteBuffer += str.slice(0, pasteEnd);
|
|
674
|
+
const afterPaste = str.slice(pasteEnd + 6);
|
|
675
|
+
isPasting = false;
|
|
676
|
+
|
|
677
|
+
const pasted = pasteBuffer.replace(/[\r\n]+$/, "");
|
|
678
|
+
pasteBuffer = "";
|
|
679
|
+
|
|
680
|
+
if (pasted.includes("\n") || pasted.includes("\r")) {
|
|
681
|
+
// Multi-line paste: use backslash continuation for each internal line
|
|
682
|
+
const lines = pasted.split(/\r?\n/);
|
|
683
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
684
|
+
originalStdinEmit(event, Buffer.from(lines[i] + "\\\r"));
|
|
685
|
+
}
|
|
686
|
+
// Last line: insert without auto-submit
|
|
687
|
+
originalStdinEmit(event, Buffer.from(lines[lines.length - 1]));
|
|
688
|
+
} else {
|
|
689
|
+
originalStdinEmit(event, Buffer.from(pasted));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (afterPaste) {
|
|
693
|
+
return originalStdinEmit(event, Buffer.from(afterPaste));
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
} else {
|
|
697
|
+
pasteBuffer += str;
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// --- Shift+Enter (Kitty protocol: \x1b[13;2u) ---
|
|
646
703
|
if (str.includes("\x1b[13;2u")) {
|
|
647
|
-
// Shift+Enter detected — append backslash and submit for continuation
|
|
648
|
-
pendingShiftEnter = true;
|
|
649
|
-
// Replace the Kitty sequence with backslash + carriage return
|
|
650
704
|
const replaced = str.replace(/\x1b\[13;2u/g, "\\\r");
|
|
651
705
|
return originalStdinEmit(event, Buffer.from(replaced));
|
|
652
706
|
}
|
|
707
|
+
|
|
708
|
+
// --- Alt+Enter (universal fallback: ESC + CR) ---
|
|
709
|
+
if (str === "\x1b\r" || str === "\x1b\n") {
|
|
710
|
+
return originalStdinEmit(event, Buffer.from("\\\r"));
|
|
711
|
+
}
|
|
653
712
|
}
|
|
654
713
|
return originalStdinEmit(event, ...args);
|
|
655
714
|
} as any;
|
|
@@ -714,9 +773,9 @@ async function chat(explicitSession?: string): Promise<void> {
|
|
|
714
773
|
|
|
715
774
|
let continuationBuffer: string[] = [];
|
|
716
775
|
|
|
717
|
-
// Handle Ctrl+C gracefully
|
|
776
|
+
// Handle Ctrl+C gracefully
|
|
718
777
|
rl.on("close", () => {
|
|
719
|
-
process.stdout.write("\x1b[<u"); // disable
|
|
778
|
+
process.stdout.write("\x1b[?2004l\x1b[<u"); // disable bracketed paste + Kitty
|
|
720
779
|
console.log(dim("\nBye."));
|
|
721
780
|
process.exit(0);
|
|
722
781
|
});
|