groundcrew-cli 0.15.15 → 0.15.17
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 +396 -280
- package/package.json +1 -1
- package/src/index.ts +371 -342
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import{createRequire}from'module';const require=createRequire(import.meta.url);
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import { existsSync } from "fs";
|
|
@@ -429,162 +429,338 @@ var CHAT_COMMANDS = [
|
|
|
429
429
|
{ cmd: "/clear", desc: "Clear pending tasks" },
|
|
430
430
|
{ cmd: "/exit", desc: "Exit chat" }
|
|
431
431
|
];
|
|
432
|
-
function
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
432
|
+
function readMultilineInput(sessionId) {
|
|
433
|
+
return new Promise((resolve) => {
|
|
434
|
+
const lines = [""];
|
|
435
|
+
let crow = 0;
|
|
436
|
+
let ccol = 0;
|
|
437
|
+
const padWidth = sessionId.length + 5;
|
|
438
|
+
let lastTermRow = 0;
|
|
439
|
+
let pasteBuffer = "";
|
|
440
|
+
let isPasting = false;
|
|
441
|
+
const fullText = () => lines.join("\n").trim();
|
|
442
|
+
const render = () => {
|
|
443
|
+
const buf = [];
|
|
444
|
+
if (lastTermRow > 0) buf.push(`\x1B[${lastTermRow}A`);
|
|
445
|
+
buf.push("\r\x1B[J");
|
|
446
|
+
const termW = process.stdout.columns || 80;
|
|
447
|
+
const info = ` ${sessionId} `;
|
|
448
|
+
const dashRight = "\u2500".repeat(Math.max(0, termW - 4 - info.length));
|
|
449
|
+
buf.push(dim("\u2500\u2500\u2500" + info + dashRight));
|
|
450
|
+
for (let i = 0; i < lines.length; i++) {
|
|
451
|
+
buf.push("\n");
|
|
452
|
+
if (i === 0) {
|
|
453
|
+
buf.push(dim(`[${sessionId}]`) + " " + bold(">") + " " + lines[i]);
|
|
454
|
+
} else {
|
|
455
|
+
buf.push(" ".repeat(padWidth) + lines[i]);
|
|
456
|
+
}
|
|
454
457
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
buf.push(
|
|
461
|
-
|
|
462
|
-
buf.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
458
|
+
const lastRow = lines.length - 1;
|
|
459
|
+
const rowsUp = lastRow - crow;
|
|
460
|
+
if (rowsUp > 0) buf.push(`\x1B[${rowsUp}A`);
|
|
461
|
+
buf.push("\r");
|
|
462
|
+
const col = padWidth + ccol;
|
|
463
|
+
if (col > 0) buf.push(`\x1B[${col}C`);
|
|
464
|
+
lastTermRow = 1 + crow;
|
|
465
|
+
process.stdout.write(buf.join(""));
|
|
466
|
+
};
|
|
467
|
+
const finish = (result) => {
|
|
468
|
+
process.stdin.removeListener("data", onData);
|
|
469
|
+
resolve(result);
|
|
470
|
+
};
|
|
471
|
+
const submit = () => {
|
|
472
|
+
const text = fullText();
|
|
473
|
+
const lastRow = lines.length - 1;
|
|
474
|
+
const rowsDown = lastRow - crow;
|
|
475
|
+
const buf = [];
|
|
476
|
+
if (rowsDown > 0) buf.push(`\x1B[${rowsDown}B`);
|
|
477
|
+
buf.push("\r");
|
|
478
|
+
const endCol = padWidth + lines[lastRow].length;
|
|
479
|
+
if (endCol > 0) buf.push(`\x1B[${endCol}C`);
|
|
480
|
+
buf.push("\n");
|
|
481
|
+
process.stdout.write(buf.join(""));
|
|
482
|
+
lastTermRow = 0;
|
|
483
|
+
finish(text || null);
|
|
484
|
+
};
|
|
485
|
+
const insertText = (text) => {
|
|
486
|
+
const chunks = text.split(/\r?\n/);
|
|
487
|
+
const before = lines[crow].slice(0, ccol);
|
|
488
|
+
const after = lines[crow].slice(ccol);
|
|
489
|
+
if (chunks.length === 1) {
|
|
490
|
+
lines[crow] = before + chunks[0] + after;
|
|
491
|
+
ccol += chunks[0].length;
|
|
492
|
+
} else {
|
|
493
|
+
lines[crow] = before + chunks[0];
|
|
494
|
+
const middle = chunks.slice(1, -1);
|
|
495
|
+
const last = chunks[chunks.length - 1];
|
|
496
|
+
lines.splice(crow + 1, 0, ...middle, last + after);
|
|
497
|
+
crow += chunks.length - 1;
|
|
498
|
+
ccol = last.length;
|
|
491
499
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
render();
|
|
501
|
+
};
|
|
502
|
+
const insertNewline = () => {
|
|
503
|
+
const before = lines[crow].slice(0, ccol);
|
|
504
|
+
const after = lines[crow].slice(ccol);
|
|
505
|
+
lines[crow] = before;
|
|
506
|
+
lines.splice(crow + 1, 0, after);
|
|
507
|
+
crow++;
|
|
508
|
+
ccol = 0;
|
|
509
|
+
render();
|
|
510
|
+
};
|
|
511
|
+
const doBackspace = () => {
|
|
512
|
+
if (ccol > 0) {
|
|
513
|
+
lines[crow] = lines[crow].slice(0, ccol - 1) + lines[crow].slice(ccol);
|
|
514
|
+
ccol--;
|
|
515
|
+
} else if (crow > 0) {
|
|
516
|
+
const prevLen = lines[crow - 1].length;
|
|
517
|
+
lines[crow - 1] += lines[crow];
|
|
518
|
+
lines.splice(crow, 1);
|
|
519
|
+
crow--;
|
|
520
|
+
ccol = prevLen;
|
|
501
521
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
522
|
+
render();
|
|
523
|
+
};
|
|
524
|
+
const doDelete = () => {
|
|
525
|
+
if (ccol < lines[crow].length) {
|
|
526
|
+
lines[crow] = lines[crow].slice(0, ccol) + lines[crow].slice(ccol + 1);
|
|
527
|
+
} else if (crow < lines.length - 1) {
|
|
528
|
+
lines[crow] += lines[crow + 1];
|
|
529
|
+
lines.splice(crow + 1, 1);
|
|
530
|
+
}
|
|
531
|
+
render();
|
|
532
|
+
};
|
|
533
|
+
const processKeys = (str) => {
|
|
534
|
+
let i = 0;
|
|
535
|
+
while (i < str.length) {
|
|
536
|
+
if (str.startsWith("\x1B[13;2u", i)) {
|
|
537
|
+
insertNewline();
|
|
538
|
+
i += 7;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (i + 1 < str.length && str[i] === "\x1B" && (str[i + 1] === "\r" || str[i + 1] === "\n")) {
|
|
542
|
+
insertNewline();
|
|
543
|
+
i += 2;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (str.startsWith("\x1B[A", i)) {
|
|
547
|
+
if (crow > 0) {
|
|
548
|
+
crow--;
|
|
549
|
+
ccol = Math.min(ccol, lines[crow].length);
|
|
550
|
+
render();
|
|
551
|
+
}
|
|
552
|
+
i += 3;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (str.startsWith("\x1B[B", i)) {
|
|
556
|
+
if (crow < lines.length - 1) {
|
|
557
|
+
crow++;
|
|
558
|
+
ccol = Math.min(ccol, lines[crow].length);
|
|
559
|
+
render();
|
|
560
|
+
}
|
|
561
|
+
i += 3;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (str.startsWith("\x1B[C", i)) {
|
|
565
|
+
if (ccol < lines[crow].length) ccol++;
|
|
566
|
+
else if (crow < lines.length - 1) {
|
|
567
|
+
crow++;
|
|
568
|
+
ccol = 0;
|
|
569
|
+
}
|
|
570
|
+
render();
|
|
571
|
+
i += 3;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (str.startsWith("\x1B[D", i)) {
|
|
575
|
+
if (ccol > 0) ccol--;
|
|
576
|
+
else if (crow > 0) {
|
|
577
|
+
crow--;
|
|
578
|
+
ccol = lines[crow].length;
|
|
579
|
+
}
|
|
580
|
+
render();
|
|
581
|
+
i += 3;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (str.startsWith("\x1B[3~", i)) {
|
|
585
|
+
doDelete();
|
|
586
|
+
i += 4;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (str.startsWith("\x1B[H", i)) {
|
|
590
|
+
ccol = 0;
|
|
591
|
+
render();
|
|
592
|
+
i += 3;
|
|
593
|
+
continue;
|
|
526
594
|
}
|
|
527
|
-
|
|
595
|
+
if (str.startsWith("\x1B[F", i)) {
|
|
596
|
+
ccol = lines[crow].length;
|
|
597
|
+
render();
|
|
598
|
+
i += 3;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (str[i] === "\x1B" && i + 1 < str.length && str[i + 1] === "[") {
|
|
602
|
+
let j = i + 2;
|
|
603
|
+
while (j < str.length && str.charCodeAt(j) >= 48 && str.charCodeAt(j) <= 63) j++;
|
|
604
|
+
if (j < str.length) j++;
|
|
605
|
+
i = j;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (str[i] === "\x1B") {
|
|
609
|
+
i++;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (str[i] === "") {
|
|
613
|
+
const hasText = fullText();
|
|
614
|
+
if (hasText || lines.length > 1 || lines[0].length > 0) {
|
|
615
|
+
const lastRow = lines.length - 1;
|
|
616
|
+
const rowsDown = lastRow - crow;
|
|
617
|
+
if (rowsDown > 0) process.stdout.write(`\x1B[${rowsDown}B`);
|
|
618
|
+
process.stdout.write("\r\n");
|
|
619
|
+
lines.length = 0;
|
|
620
|
+
lines.push("");
|
|
621
|
+
crow = 0;
|
|
622
|
+
ccol = 0;
|
|
623
|
+
lastTermRow = 0;
|
|
624
|
+
render();
|
|
625
|
+
} else {
|
|
626
|
+
process.stdout.write("\r\n");
|
|
627
|
+
finish(null);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
i++;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (str[i] === "") {
|
|
634
|
+
if (fullText()) {
|
|
635
|
+
doDelete();
|
|
636
|
+
} else {
|
|
637
|
+
process.stdout.write("\n");
|
|
638
|
+
finish(null);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
i++;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (str[i] === "") {
|
|
645
|
+
ccol = 0;
|
|
646
|
+
render();
|
|
647
|
+
i++;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (str[i] === "") {
|
|
651
|
+
ccol = lines[crow].length;
|
|
652
|
+
render();
|
|
653
|
+
i++;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (str[i] === "") {
|
|
657
|
+
lines[crow] = lines[crow].slice(ccol);
|
|
658
|
+
ccol = 0;
|
|
659
|
+
render();
|
|
660
|
+
i++;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (str[i] === "\v") {
|
|
664
|
+
lines[crow] = lines[crow].slice(0, ccol);
|
|
665
|
+
render();
|
|
666
|
+
i++;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (str[i] === "") {
|
|
670
|
+
const before = lines[crow].slice(0, ccol);
|
|
671
|
+
const stripped = before.replace(/\s+$/, "");
|
|
672
|
+
const sp = stripped.lastIndexOf(" ");
|
|
673
|
+
const newBefore = sp >= 0 ? stripped.slice(0, sp + 1) : "";
|
|
674
|
+
lines[crow] = newBefore + lines[crow].slice(ccol);
|
|
675
|
+
ccol = newBefore.length;
|
|
676
|
+
render();
|
|
677
|
+
i++;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (str[i] === "\n") {
|
|
681
|
+
insertNewline();
|
|
682
|
+
i++;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (str[i] === "\r") {
|
|
686
|
+
submit();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (str[i] === "\x7F" || str[i] === "\b") {
|
|
690
|
+
doBackspace();
|
|
691
|
+
i++;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (str[i] === " ") {
|
|
695
|
+
const currentLine = lines[crow];
|
|
696
|
+
if (lines.length === 1 && currentLine.startsWith("/")) {
|
|
697
|
+
const partial = currentLine.split(" ")[0];
|
|
698
|
+
const matches = CHAT_COMMANDS.filter((c) => c.cmd.startsWith(partial));
|
|
699
|
+
if (matches.length === 1) {
|
|
700
|
+
lines[0] = matches[0].cmd + " ";
|
|
701
|
+
ccol = lines[0].length;
|
|
702
|
+
render();
|
|
703
|
+
} else if (matches.length > 1) {
|
|
704
|
+
const lastRow = lines.length - 1;
|
|
705
|
+
const rowsDown = lastRow - crow;
|
|
706
|
+
if (rowsDown > 0) process.stdout.write(`\x1B[${rowsDown}B`);
|
|
707
|
+
process.stdout.write("\r\n");
|
|
708
|
+
for (const m of matches) {
|
|
709
|
+
process.stdout.write(` ${cyan(m.cmd.padEnd(14))} ${dim(m.desc)}
|
|
710
|
+
`);
|
|
711
|
+
}
|
|
712
|
+
lastTermRow = 0;
|
|
713
|
+
render();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
i++;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const code = str.charCodeAt(i);
|
|
720
|
+
if (code >= 32) {
|
|
721
|
+
lines[crow] = lines[crow].slice(0, ccol) + str[i] + lines[crow].slice(ccol);
|
|
722
|
+
ccol++;
|
|
723
|
+
render();
|
|
724
|
+
}
|
|
725
|
+
i++;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const onData = (data) => {
|
|
729
|
+
let str = data.toString();
|
|
730
|
+
const ps = str.indexOf("\x1B[200~");
|
|
731
|
+
if (ps !== -1) {
|
|
732
|
+
isPasting = true;
|
|
733
|
+
const before = str.slice(0, ps);
|
|
734
|
+
if (before) processKeys(before);
|
|
735
|
+
str = str.slice(ps + 6);
|
|
528
736
|
}
|
|
529
737
|
if (isPasting) {
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
pasteBuffer += str.slice(0,
|
|
533
|
-
const afterPaste = str.slice(pasteEnd + 6);
|
|
738
|
+
const pe = str.indexOf("\x1B[201~");
|
|
739
|
+
if (pe !== -1) {
|
|
740
|
+
pasteBuffer += str.slice(0, pe);
|
|
534
741
|
isPasting = false;
|
|
535
742
|
const pasted = pasteBuffer.replace(/[\r\n]+$/, "");
|
|
536
743
|
pasteBuffer = "";
|
|
537
|
-
if (pasted
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
originalStdinEmit(event, Buffer.from(lines[i] + "\\\r"));
|
|
541
|
-
}
|
|
542
|
-
originalStdinEmit(event, Buffer.from(lines[lines.length - 1]));
|
|
543
|
-
} else {
|
|
544
|
-
originalStdinEmit(event, Buffer.from(pasted));
|
|
545
|
-
}
|
|
546
|
-
if (afterPaste) {
|
|
547
|
-
return originalStdinEmit(event, Buffer.from(afterPaste));
|
|
548
|
-
}
|
|
549
|
-
return false;
|
|
744
|
+
if (pasted) insertText(pasted);
|
|
745
|
+
const after = str.slice(pe + 6);
|
|
746
|
+
if (after) processKeys(after);
|
|
550
747
|
} else {
|
|
551
748
|
pasteBuffer += str;
|
|
552
|
-
return false;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (str.includes("\x1B[13;2u")) {
|
|
556
|
-
const replaced = str.replace(/\x1b\[13;2u/g, "\\\r");
|
|
557
|
-
return originalStdinEmit(event, Buffer.from(replaced));
|
|
558
|
-
}
|
|
559
|
-
if (str === "\x1B\r" || str === "\x1B\n") {
|
|
560
|
-
return originalStdinEmit(event, Buffer.from("\\\r"));
|
|
561
|
-
}
|
|
562
|
-
if (str === "\n") {
|
|
563
|
-
return originalStdinEmit(event, Buffer.from("\\\r"));
|
|
564
|
-
}
|
|
565
|
-
if ((str === "\x7F" || str === "\b") && continuationBuffer.length > 0) {
|
|
566
|
-
const currentLine = rl.line;
|
|
567
|
-
const cursor = rl.cursor;
|
|
568
|
-
if (cursor === 0 && currentLine.length === 0) {
|
|
569
|
-
const prevLine = continuationBuffer.pop();
|
|
570
|
-
rl.line = prevLine;
|
|
571
|
-
rl.cursor = prevLine.length;
|
|
572
|
-
const isCont = continuationBuffer.length > 0;
|
|
573
|
-
const prefix = isCont ? `${dim(`[${current.id}]`)} ${dim("...")} ` : `${dim(`[${current.id}]`)} ${bold(">")} `;
|
|
574
|
-
rl.setPrompt(prefix);
|
|
575
|
-
rl._refreshLine();
|
|
576
|
-
return false;
|
|
577
749
|
}
|
|
750
|
+
return;
|
|
578
751
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
752
|
+
processKeys(str);
|
|
753
|
+
};
|
|
754
|
+
process.stdin.on("data", onData);
|
|
755
|
+
render();
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
async function chat(explicitSession) {
|
|
759
|
+
process.stdout.write("\x1B[?2004h\x1B[>1u");
|
|
582
760
|
const rl = readline.createInterface({
|
|
583
761
|
input: process.stdin,
|
|
584
|
-
output: process.stdout
|
|
585
|
-
completer: chatCompleter
|
|
762
|
+
output: process.stdout
|
|
586
763
|
});
|
|
587
|
-
setupInlineSuggestions(rl);
|
|
588
764
|
let current = null;
|
|
589
765
|
if (explicitSession) {
|
|
590
766
|
const dir = path.join(SESSIONS_DIR, explicitSession);
|
|
@@ -601,6 +777,7 @@ async function chat(explicitSession) {
|
|
|
601
777
|
return;
|
|
602
778
|
}
|
|
603
779
|
}
|
|
780
|
+
rl.close();
|
|
604
781
|
const projectName = path.basename(current.cwd);
|
|
605
782
|
const banner = [
|
|
606
783
|
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 ",
|
|
@@ -633,152 +810,91 @@ async function chat(explicitSession) {
|
|
|
633
810
|
console.log(dim(" \u2502") + " ".repeat(W) + dim("\u2502"));
|
|
634
811
|
console.log(dim(" \u2570" + "\u2500".repeat(W) + "\u256F"));
|
|
635
812
|
console.log();
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (line || continuationBuffer.length > 0) {
|
|
640
|
-
continuationBuffer = [];
|
|
641
|
-
process.stdout.write("\n");
|
|
642
|
-
prompt();
|
|
643
|
-
} else {
|
|
644
|
-
process.stdout.write("\x1B[?2004l\x1B[<u");
|
|
645
|
-
console.log(dim("\nBye."));
|
|
646
|
-
process.exit(0);
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
rl.on("close", () => {
|
|
813
|
+
process.stdin.setRawMode(true);
|
|
814
|
+
process.stdin.resume();
|
|
815
|
+
const exitChat = () => {
|
|
650
816
|
process.stdout.write("\x1B[?2004l\x1B[<u");
|
|
651
|
-
|
|
817
|
+
process.stdin.setRawMode(false);
|
|
818
|
+
console.log(dim("Bye."));
|
|
652
819
|
process.exit(0);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
await add(fullText, 0, current.dir);
|
|
672
|
-
} else {
|
|
673
|
-
await add(fullText, 0, current.dir);
|
|
674
|
-
}
|
|
675
|
-
} catch (err) {
|
|
676
|
-
console.error(red(err.message));
|
|
677
|
-
}
|
|
820
|
+
};
|
|
821
|
+
while (true) {
|
|
822
|
+
const text = await readMultilineInput(current.id);
|
|
823
|
+
if (text === null) exitChat();
|
|
824
|
+
if (!text) continue;
|
|
825
|
+
const trimmed = text.trim();
|
|
826
|
+
try {
|
|
827
|
+
if (trimmed === "/quit" || trimmed === "/exit") exitChat();
|
|
828
|
+
if (trimmed === "/sessions") {
|
|
829
|
+
const choices = await listSessionChoices();
|
|
830
|
+
if (choices.length === 0) {
|
|
831
|
+
console.log(dim("No active sessions."));
|
|
832
|
+
} else {
|
|
833
|
+
choices.forEach((s, i) => {
|
|
834
|
+
const marker = s.id === current.id ? green("*") : " ";
|
|
835
|
+
const pName = path.basename(s.cwd);
|
|
836
|
+
console.log(` ${marker} ${bold(String(i + 1))}. ${cyan(s.id)} ${dim(pName)} | ${s.status} | ${s.minutes}min | ${s.tasks} done`);
|
|
837
|
+
});
|
|
678
838
|
}
|
|
679
|
-
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const trimmed = line.trim();
|
|
683
|
-
if (!trimmed) {
|
|
684
|
-
prompt();
|
|
685
|
-
return;
|
|
839
|
+
continue;
|
|
686
840
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (trimmed === "/sessions") {
|
|
694
|
-
const choices = await listSessionChoices();
|
|
695
|
-
if (choices.length === 0) {
|
|
696
|
-
console.log(dim("No active sessions."));
|
|
697
|
-
} else {
|
|
698
|
-
choices.forEach((s, i) => {
|
|
699
|
-
const marker = s.id === current.id ? green("*") : " ";
|
|
700
|
-
const pName = path.basename(s.cwd);
|
|
701
|
-
console.log(` ${marker} ${bold(String(i + 1))}. ${cyan(s.id)} ${dim(pName)} | ${s.status} | ${s.minutes}min | ${s.tasks} done`);
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
prompt();
|
|
705
|
-
return;
|
|
841
|
+
if (trimmed.startsWith("/switch")) {
|
|
842
|
+
const arg = trimmed.slice(7).trim();
|
|
843
|
+
const choices = await listSessionChoices();
|
|
844
|
+
if (choices.length === 0) {
|
|
845
|
+
console.log(red("No active sessions."));
|
|
846
|
+
continue;
|
|
706
847
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (idx >= 0 && idx < choices.length) {
|
|
717
|
-
current = choices[idx];
|
|
718
|
-
console.log(green(`Switched to ${current.id} (${path.basename(current.cwd)})`));
|
|
719
|
-
} else {
|
|
720
|
-
choices.forEach((s, i) => {
|
|
721
|
-
const marker = s.id === current.id ? green("*") : " ";
|
|
722
|
-
console.log(` ${marker} ${bold(String(i + 1))}. ${cyan(s.id)} ${dim(path.basename(s.cwd))}`);
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
prompt();
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
if (trimmed === "/status") {
|
|
729
|
-
await status(current.dir);
|
|
730
|
-
prompt();
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (trimmed === "/history") {
|
|
734
|
-
await history();
|
|
735
|
-
prompt();
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
if (trimmed.startsWith("/feedback ")) {
|
|
739
|
-
const msg = trimmed.slice(10).trim();
|
|
740
|
-
if (msg) {
|
|
741
|
-
await feedback(msg, current.dir);
|
|
742
|
-
} else {
|
|
743
|
-
console.log(red("Usage: /feedback <message>"));
|
|
744
|
-
}
|
|
745
|
-
prompt();
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
if (trimmed.startsWith("/priority ")) {
|
|
749
|
-
const task = trimmed.slice(10).trim();
|
|
750
|
-
if (task) {
|
|
751
|
-
await add(task, 9, current.dir);
|
|
752
|
-
} else {
|
|
753
|
-
console.log(red("Usage: /priority <task>"));
|
|
754
|
-
}
|
|
755
|
-
prompt();
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
if (trimmed === "/queue") {
|
|
759
|
-
await listQueueCmd(current.dir);
|
|
760
|
-
prompt();
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
if (trimmed === "/clear") {
|
|
764
|
-
await clear(current.dir);
|
|
765
|
-
prompt();
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
if (trimmed.startsWith("/")) {
|
|
769
|
-
console.log(red(`Unknown command: ${trimmed.split(" ")[0]}`));
|
|
770
|
-
console.log(dim(" Press Tab to see available commands"));
|
|
771
|
-
prompt();
|
|
772
|
-
return;
|
|
848
|
+
const idx = parseInt(arg) - 1;
|
|
849
|
+
if (idx >= 0 && idx < choices.length) {
|
|
850
|
+
current = choices[idx];
|
|
851
|
+
console.log(green(`Switched to ${current.id} (${path.basename(current.cwd)})`));
|
|
852
|
+
} else {
|
|
853
|
+
choices.forEach((s, i) => {
|
|
854
|
+
const marker = s.id === current.id ? green("*") : " ";
|
|
855
|
+
console.log(` ${marker} ${bold(String(i + 1))}. ${cyan(s.id)} ${dim(path.basename(s.cwd))}`);
|
|
856
|
+
});
|
|
773
857
|
}
|
|
774
|
-
|
|
775
|
-
} catch (err) {
|
|
776
|
-
console.error(red(err.message));
|
|
858
|
+
continue;
|
|
777
859
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
860
|
+
if (trimmed === "/status") {
|
|
861
|
+
await status(current.dir);
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (trimmed === "/history") {
|
|
865
|
+
await history();
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (trimmed.startsWith("/feedback ")) {
|
|
869
|
+
const msg = trimmed.slice(10).trim();
|
|
870
|
+
if (msg) await feedback(msg, current.dir);
|
|
871
|
+
else console.log(red("Usage: /feedback <message>"));
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (trimmed.startsWith("/priority ")) {
|
|
875
|
+
const task = trimmed.slice(10).trim();
|
|
876
|
+
if (task) await add(task, 9, current.dir);
|
|
877
|
+
else console.log(red("Usage: /priority <task>"));
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (trimmed === "/queue") {
|
|
881
|
+
await listQueueCmd(current.dir);
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (trimmed === "/clear") {
|
|
885
|
+
await clear(current.dir);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (trimmed.startsWith("/")) {
|
|
889
|
+
console.log(red(`Unknown command: ${trimmed.split(" ")[0]}`));
|
|
890
|
+
console.log(dim(" Press Tab to see available commands"));
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
await add(trimmed, 0, current.dir);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
console.error(red(err.message));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
782
898
|
}
|
|
783
899
|
function usage() {
|
|
784
900
|
console.log(`
|