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