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.
Files changed (3) hide show
  1. package/dist/index.js +396 -280
  2. package/package.json +1 -1
  3. 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 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
+ 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
- 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`);
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
- 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`);
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
- 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)));
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
- str = str.slice(pasteStart + 6);
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 pasteEnd = str.indexOf("\x1B[201~");
531
- if (pasteEnd !== -1) {
532
- pasteBuffer += str.slice(0, pasteEnd);
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.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;
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
- return originalStdinEmit(event, ...args);
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
- 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", () => {
813
+ process.stdin.setRawMode(true);
814
+ process.stdin.resume();
815
+ const exitChat = () => {
650
816
  process.stdout.write("\x1B[?2004l\x1B[<u");
651
- console.log(dim("\nBye."));
817
+ process.stdin.setRawMode(false);
818
+ console.log(dim("Bye."));
652
819
  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
- }
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
- prompt();
680
- return;
681
- }
682
- const trimmed = line.trim();
683
- if (!trimmed) {
684
- prompt();
685
- return;
839
+ continue;
686
840
  }
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;
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
- 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;
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
- await add(trimmed, 0, current.dir);
775
- } catch (err) {
776
- console.error(red(err.message));
858
+ continue;
777
859
  }
778
- prompt();
779
- });
780
- };
781
- prompt();
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(`