worktree-launcher 1.4.1 → 1.5.0
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/README.md +8 -8
- package/dist/index.js +103 -331
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,14 +44,15 @@ Run `wt` with no arguments to open a terminal UI:
|
|
|
44
44
|
wt
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
The TUI shows your repo name, current branch, and all existing worktrees.
|
|
47
|
+
The TUI shows your repo name, current branch, and all existing worktrees.
|
|
48
48
|
|
|
49
49
|
| Key | Action |
|
|
50
50
|
|-----|--------|
|
|
51
|
-
| `n` | Create new worktree
|
|
51
|
+
| `n` | Create new worktree |
|
|
52
52
|
| `d` | Delete selected worktree |
|
|
53
53
|
| `c` | Launch Claude Code in selected worktree |
|
|
54
54
|
| `x` | Launch Codex in selected worktree |
|
|
55
|
+
| `p` | Push selected branch to remote |
|
|
55
56
|
| `Enter` | Print cd command for selected worktree |
|
|
56
57
|
| `r` | Refresh worktree list |
|
|
57
58
|
| `q` | Quit |
|
|
@@ -60,13 +61,12 @@ Navigate with arrow keys or vim-style `j`/`k`.
|
|
|
60
61
|
|
|
61
62
|
### Creating a New Worktree
|
|
62
63
|
|
|
63
|
-
Press `n` to
|
|
64
|
+
Press `n` to create a new worktree:
|
|
64
65
|
|
|
65
|
-
1.
|
|
66
|
-
2.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
5. **Launch AI tool** - Choose Claude Code, Codex, or skip
|
|
66
|
+
1. Enter the branch name
|
|
67
|
+
2. Choose AI tool to launch (Claude Code, Codex, or Skip)
|
|
68
|
+
|
|
69
|
+
The worktree is created from your current branch, .env files are copied automatically.
|
|
70
70
|
|
|
71
71
|
## Commands
|
|
72
72
|
|
package/dist/index.js
CHANGED
|
@@ -62,11 +62,13 @@ async function getDefaultBranch() {
|
|
|
62
62
|
return "main";
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
async function createWorktree(worktreePath, branchName) {
|
|
65
|
+
async function createWorktree(worktreePath, branchName, startPoint) {
|
|
66
66
|
validateBranchName(branchName);
|
|
67
67
|
const exists = await branchExists(branchName);
|
|
68
68
|
if (exists) {
|
|
69
69
|
await execFileAsync("git", ["worktree", "add", "--", worktreePath, branchName]);
|
|
70
|
+
} else if (startPoint) {
|
|
71
|
+
await execFileAsync("git", ["worktree", "add", "-b", branchName, "--", worktreePath, startPoint]);
|
|
70
72
|
} else {
|
|
71
73
|
await execFileAsync("git", ["worktree", "add", "-b", branchName, "--", worktreePath]);
|
|
72
74
|
}
|
|
@@ -111,8 +113,8 @@ async function pruneWorktrees() {
|
|
|
111
113
|
}
|
|
112
114
|
async function isBranchMerged(branchName) {
|
|
113
115
|
try {
|
|
114
|
-
const
|
|
115
|
-
const { stdout } = await execFileAsync("git", ["branch", "--merged",
|
|
116
|
+
const defaultBranch = await getDefaultBranch();
|
|
117
|
+
const { stdout } = await execFileAsync("git", ["branch", "--merged", defaultBranch]);
|
|
116
118
|
const mergedBranches = stdout.split("\n").map((b) => b.trim().replace("* ", ""));
|
|
117
119
|
return mergedBranches.includes(branchName);
|
|
118
120
|
} catch {
|
|
@@ -609,11 +611,8 @@ import path8 from "path";
|
|
|
609
611
|
var screen;
|
|
610
612
|
var worktreeList;
|
|
611
613
|
var statusBar;
|
|
612
|
-
var helpBar;
|
|
613
|
-
var headerBox;
|
|
614
614
|
var mainRepoPath;
|
|
615
615
|
var currentBranch;
|
|
616
|
-
var defaultBranch;
|
|
617
616
|
var worktrees = [];
|
|
618
617
|
var selectedIndex = 0;
|
|
619
618
|
async function interactiveCommand() {
|
|
@@ -623,7 +622,6 @@ async function interactiveCommand() {
|
|
|
623
622
|
}
|
|
624
623
|
mainRepoPath = await getGitRoot();
|
|
625
624
|
currentBranch = await getCurrentBranch();
|
|
626
|
-
defaultBranch = await getDefaultBranch();
|
|
627
625
|
const repoName = path8.basename(mainRepoPath);
|
|
628
626
|
screen = blessed.screen({
|
|
629
627
|
smartCSR: true,
|
|
@@ -632,17 +630,14 @@ async function interactiveCommand() {
|
|
|
632
630
|
terminal: "xterm-256color",
|
|
633
631
|
warnings: false
|
|
634
632
|
});
|
|
635
|
-
|
|
633
|
+
blessed.box({
|
|
636
634
|
parent: screen,
|
|
637
635
|
top: 0,
|
|
638
636
|
left: 0,
|
|
639
637
|
width: "100%",
|
|
640
638
|
height: 1,
|
|
641
639
|
content: ` ${repoName} (${currentBranch})`,
|
|
642
|
-
style: {
|
|
643
|
-
fg: "black",
|
|
644
|
-
bg: "cyan"
|
|
645
|
-
}
|
|
640
|
+
style: { fg: "black", bg: "cyan" }
|
|
646
641
|
});
|
|
647
642
|
worktreeList = blessed.list({
|
|
648
643
|
parent: screen,
|
|
@@ -654,18 +649,10 @@ async function interactiveCommand() {
|
|
|
654
649
|
vi: true,
|
|
655
650
|
mouse: true,
|
|
656
651
|
style: {
|
|
657
|
-
selected: {
|
|
658
|
-
|
|
659
|
-
fg: "black"
|
|
660
|
-
},
|
|
661
|
-
item: {
|
|
662
|
-
fg: "default"
|
|
663
|
-
}
|
|
652
|
+
selected: { bg: "cyan", fg: "black" },
|
|
653
|
+
item: { fg: "default" }
|
|
664
654
|
},
|
|
665
|
-
scrollbar: {
|
|
666
|
-
ch: " ",
|
|
667
|
-
style: { bg: "cyan" }
|
|
668
|
-
}
|
|
655
|
+
scrollbar: { ch: " ", style: { bg: "cyan" } }
|
|
669
656
|
});
|
|
670
657
|
statusBar = blessed.box({
|
|
671
658
|
parent: screen,
|
|
@@ -674,52 +661,34 @@ async function interactiveCommand() {
|
|
|
674
661
|
width: "100%",
|
|
675
662
|
height: 1,
|
|
676
663
|
content: "",
|
|
677
|
-
style: {
|
|
678
|
-
fg: "green"
|
|
679
|
-
}
|
|
664
|
+
style: { fg: "green" }
|
|
680
665
|
});
|
|
681
|
-
|
|
666
|
+
blessed.box({
|
|
682
667
|
parent: screen,
|
|
683
668
|
bottom: 0,
|
|
684
669
|
left: 0,
|
|
685
670
|
width: "100%",
|
|
686
671
|
height: 1,
|
|
687
|
-
content: " [n]ew [d]elete [c]laude [x]codex [Enter]cd [q]uit",
|
|
688
|
-
style: {
|
|
689
|
-
fg: "black",
|
|
690
|
-
bg: "cyan"
|
|
691
|
-
}
|
|
672
|
+
content: " [n]ew [d]elete [c]laude [x]codex [p]ush [Enter]cd [q]uit",
|
|
673
|
+
style: { fg: "black", bg: "cyan" }
|
|
692
674
|
});
|
|
693
675
|
await refreshWorktrees();
|
|
694
676
|
worktreeList.on("select", (_item, index) => {
|
|
695
677
|
selectedIndex = index;
|
|
696
678
|
showPath();
|
|
697
679
|
});
|
|
698
|
-
screen.key(["q", "C-c"], () =>
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
screen.key(["
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
screen.key(["
|
|
705
|
-
await deleteSelected();
|
|
706
|
-
});
|
|
707
|
-
screen.key(["c"], async () => {
|
|
708
|
-
await launchAI("claude");
|
|
709
|
-
});
|
|
710
|
-
screen.key(["x"], async () => {
|
|
711
|
-
await launchAI("codex");
|
|
712
|
-
});
|
|
680
|
+
screen.key(["q", "C-c"], () => exitScreen());
|
|
681
|
+
screen.key(["n"], () => showNewWorktreeForm());
|
|
682
|
+
screen.key(["d"], () => deleteSelected());
|
|
683
|
+
screen.key(["c"], () => launchTool("claude"));
|
|
684
|
+
screen.key(["x"], () => launchTool("codex"));
|
|
685
|
+
screen.key(["p"], () => pushSelected());
|
|
686
|
+
screen.key(["r"], () => refreshWorktrees());
|
|
713
687
|
screen.key(["enter"], () => {
|
|
714
688
|
const wt = worktrees[selectedIndex];
|
|
715
|
-
if (wt)
|
|
716
|
-
cleanExit(`
|
|
689
|
+
if (wt) exitScreen(`
|
|
717
690
|
cd "${wt.path}"
|
|
718
691
|
`);
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
screen.key(["r"], async () => {
|
|
722
|
-
await refreshWorktrees();
|
|
723
692
|
});
|
|
724
693
|
worktreeList.focus();
|
|
725
694
|
screen.render();
|
|
@@ -731,116 +700,92 @@ async function refreshWorktrees() {
|
|
|
731
700
|
const isMain = wt.path === mainRepoPath;
|
|
732
701
|
const dirName = path8.basename(wt.path);
|
|
733
702
|
const branch = wt.branch || "(detached)";
|
|
734
|
-
const
|
|
735
|
-
return ` ${dirName.padEnd(40)} ${branch.padEnd(25)} ${
|
|
703
|
+
const tag = isMain ? "[main]" : "";
|
|
704
|
+
return ` ${dirName.padEnd(40)} ${branch.padEnd(25)} ${tag}`;
|
|
736
705
|
});
|
|
737
706
|
worktreeList.setItems(items);
|
|
738
707
|
worktreeList.select(selectedIndex);
|
|
739
708
|
showPath();
|
|
740
|
-
screen.render();
|
|
741
709
|
}
|
|
742
710
|
function showPath() {
|
|
743
711
|
const wt = worktrees[selectedIndex];
|
|
744
|
-
|
|
745
|
-
setStatus(wt.path);
|
|
746
|
-
} else {
|
|
747
|
-
setStatus("");
|
|
748
|
-
}
|
|
712
|
+
setStatus(wt ? wt.path : "");
|
|
749
713
|
}
|
|
750
714
|
function setStatus(msg) {
|
|
751
715
|
statusBar.setContent(` ${msg}`);
|
|
752
716
|
screen.render();
|
|
753
717
|
}
|
|
754
|
-
function
|
|
718
|
+
function cleanupScreen() {
|
|
755
719
|
screen.program.clear();
|
|
756
720
|
screen.program.disableMouse();
|
|
757
721
|
screen.program.showCursor();
|
|
758
722
|
screen.program.normalBuffer();
|
|
759
723
|
screen.destroy();
|
|
760
|
-
if (message) {
|
|
761
|
-
console.log(message);
|
|
762
|
-
}
|
|
763
|
-
process.exit(0);
|
|
764
724
|
}
|
|
765
|
-
function
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
copyEnv: true,
|
|
770
|
-
pushToRemote: false,
|
|
771
|
-
aiTool: "claude"
|
|
772
|
-
};
|
|
773
|
-
askBranchName(state);
|
|
725
|
+
function exitScreen(message) {
|
|
726
|
+
cleanupScreen();
|
|
727
|
+
if (message) console.log(message);
|
|
728
|
+
process.exit(0);
|
|
774
729
|
}
|
|
775
|
-
function
|
|
730
|
+
function showNewWorktreeForm() {
|
|
776
731
|
const form = blessed.box({
|
|
777
732
|
parent: screen,
|
|
778
733
|
top: "center",
|
|
779
734
|
left: "center",
|
|
780
735
|
width: 60,
|
|
781
|
-
height:
|
|
736
|
+
height: 10,
|
|
782
737
|
border: { type: "line" },
|
|
783
|
-
style: {
|
|
784
|
-
fg: "default",
|
|
785
|
-
border: { fg: "cyan" }
|
|
786
|
-
},
|
|
738
|
+
style: { fg: "default", border: { fg: "cyan" } },
|
|
787
739
|
label: " New Worktree "
|
|
788
740
|
});
|
|
789
741
|
blessed.text({
|
|
790
742
|
parent: form,
|
|
791
743
|
top: 1,
|
|
792
744
|
left: 2,
|
|
793
|
-
content: `Repository: ${path8.basename(mainRepoPath)}`,
|
|
745
|
+
content: `Repository: ${path8.basename(mainRepoPath)} (from ${currentBranch})`,
|
|
794
746
|
style: { fg: "cyan" }
|
|
795
747
|
});
|
|
796
748
|
blessed.text({
|
|
797
749
|
parent: form,
|
|
798
|
-
top:
|
|
799
|
-
left: 2,
|
|
800
|
-
content: `Current branch: ${currentBranch}`,
|
|
801
|
-
style: { fg: "default" }
|
|
802
|
-
});
|
|
803
|
-
blessed.text({
|
|
804
|
-
parent: form,
|
|
805
|
-
top: 4,
|
|
750
|
+
top: 3,
|
|
806
751
|
left: 2,
|
|
807
752
|
content: "Branch name:",
|
|
808
753
|
style: { fg: "default" }
|
|
809
754
|
});
|
|
810
755
|
const input = blessed.textbox({
|
|
811
756
|
parent: form,
|
|
812
|
-
top:
|
|
757
|
+
top: 4,
|
|
813
758
|
left: 2,
|
|
814
759
|
width: 54,
|
|
815
760
|
height: 1,
|
|
816
|
-
style: {
|
|
817
|
-
fg: "black",
|
|
818
|
-
bg: "white"
|
|
819
|
-
},
|
|
761
|
+
style: { fg: "black", bg: "white" },
|
|
820
762
|
inputOnFocus: true
|
|
821
763
|
});
|
|
822
764
|
blessed.text({
|
|
823
765
|
parent: form,
|
|
824
|
-
top:
|
|
766
|
+
top: 6,
|
|
825
767
|
left: 2,
|
|
826
768
|
content: "[Enter] next [Esc] cancel",
|
|
827
769
|
style: { fg: "cyan" }
|
|
828
770
|
});
|
|
829
771
|
input.focus();
|
|
830
772
|
screen.render();
|
|
831
|
-
input.on("submit", (
|
|
832
|
-
|
|
773
|
+
input.on("submit", () => {
|
|
774
|
+
const value = input.getValue()?.trim();
|
|
775
|
+
if (!value) {
|
|
833
776
|
form.destroy();
|
|
834
777
|
screen.render();
|
|
778
|
+
worktreeList.focus();
|
|
835
779
|
return;
|
|
836
780
|
}
|
|
837
781
|
try {
|
|
838
|
-
validateBranchName(value
|
|
839
|
-
state.branchName = value.trim();
|
|
782
|
+
validateBranchName(value);
|
|
840
783
|
form.destroy();
|
|
841
|
-
|
|
784
|
+
screen.render();
|
|
785
|
+
showAIToolSelector(value);
|
|
842
786
|
} catch (e) {
|
|
843
787
|
setStatus(`Error: ${e.message}`);
|
|
788
|
+
input.clearValue();
|
|
844
789
|
input.focus();
|
|
845
790
|
screen.render();
|
|
846
791
|
}
|
|
@@ -848,172 +793,19 @@ function askBranchName(state) {
|
|
|
848
793
|
input.on("cancel", () => {
|
|
849
794
|
form.destroy();
|
|
850
795
|
screen.render();
|
|
796
|
+
worktreeList.focus();
|
|
851
797
|
});
|
|
852
798
|
input.readInput();
|
|
853
799
|
}
|
|
854
|
-
function
|
|
855
|
-
const form = blessed.box({
|
|
856
|
-
parent: screen,
|
|
857
|
-
top: "center",
|
|
858
|
-
left: "center",
|
|
859
|
-
width: 50,
|
|
860
|
-
height: 10,
|
|
861
|
-
border: { type: "line" },
|
|
862
|
-
style: {
|
|
863
|
-
fg: "default",
|
|
864
|
-
border: { fg: "cyan" }
|
|
865
|
-
},
|
|
866
|
-
label: " Base Branch "
|
|
867
|
-
});
|
|
868
|
-
blessed.text({
|
|
869
|
-
parent: form,
|
|
870
|
-
top: 1,
|
|
871
|
-
left: 2,
|
|
872
|
-
content: "Create worktree from:",
|
|
873
|
-
style: { fg: "default" }
|
|
874
|
-
});
|
|
875
|
-
const list = blessed.list({
|
|
876
|
-
parent: form,
|
|
877
|
-
top: 3,
|
|
878
|
-
left: 2,
|
|
879
|
-
width: 44,
|
|
880
|
-
height: 3,
|
|
881
|
-
keys: true,
|
|
882
|
-
vi: true,
|
|
883
|
-
style: {
|
|
884
|
-
selected: { bg: "cyan", fg: "black" },
|
|
885
|
-
item: { fg: "default" }
|
|
886
|
-
},
|
|
887
|
-
items: [
|
|
888
|
-
` Current branch (${currentBranch})`,
|
|
889
|
-
` Default branch (${defaultBranch})`
|
|
890
|
-
]
|
|
891
|
-
});
|
|
892
|
-
blessed.text({
|
|
893
|
-
parent: form,
|
|
894
|
-
top: 7,
|
|
895
|
-
left: 2,
|
|
896
|
-
content: "[Enter] select [Esc] cancel",
|
|
897
|
-
style: { fg: "cyan" }
|
|
898
|
-
});
|
|
899
|
-
list.focus();
|
|
900
|
-
screen.render();
|
|
901
|
-
list.on("select", (_item, index) => {
|
|
902
|
-
state.baseBranch = index === 0 ? "current" : "default";
|
|
903
|
-
form.destroy();
|
|
904
|
-
askCopyEnv(state);
|
|
905
|
-
});
|
|
906
|
-
list.key(["escape"], () => {
|
|
907
|
-
form.destroy();
|
|
908
|
-
screen.render();
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
function askCopyEnv(state) {
|
|
912
|
-
const form = blessed.box({
|
|
913
|
-
parent: screen,
|
|
914
|
-
top: "center",
|
|
915
|
-
left: "center",
|
|
916
|
-
width: 40,
|
|
917
|
-
height: 8,
|
|
918
|
-
border: { type: "line" },
|
|
919
|
-
style: {
|
|
920
|
-
fg: "default",
|
|
921
|
-
border: { fg: "cyan" }
|
|
922
|
-
},
|
|
923
|
-
label: " Environment Files "
|
|
924
|
-
});
|
|
925
|
-
blessed.text({
|
|
926
|
-
parent: form,
|
|
927
|
-
top: 1,
|
|
928
|
-
left: 2,
|
|
929
|
-
content: "Copy .env files to worktree?",
|
|
930
|
-
style: { fg: "default" }
|
|
931
|
-
});
|
|
932
|
-
const list = blessed.list({
|
|
933
|
-
parent: form,
|
|
934
|
-
top: 3,
|
|
935
|
-
left: 2,
|
|
936
|
-
width: 34,
|
|
937
|
-
height: 2,
|
|
938
|
-
keys: true,
|
|
939
|
-
vi: true,
|
|
940
|
-
style: {
|
|
941
|
-
selected: { bg: "cyan", fg: "black" },
|
|
942
|
-
item: { fg: "default" }
|
|
943
|
-
},
|
|
944
|
-
items: [" Yes (recommended)", " No"]
|
|
945
|
-
});
|
|
946
|
-
list.focus();
|
|
947
|
-
screen.render();
|
|
948
|
-
list.on("select", (_item, index) => {
|
|
949
|
-
state.copyEnv = index === 0;
|
|
950
|
-
form.destroy();
|
|
951
|
-
askPushToRemote(state);
|
|
952
|
-
});
|
|
953
|
-
list.key(["escape"], () => {
|
|
954
|
-
form.destroy();
|
|
955
|
-
screen.render();
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
function askPushToRemote(state) {
|
|
959
|
-
const form = blessed.box({
|
|
960
|
-
parent: screen,
|
|
961
|
-
top: "center",
|
|
962
|
-
left: "center",
|
|
963
|
-
width: 45,
|
|
964
|
-
height: 8,
|
|
965
|
-
border: { type: "line" },
|
|
966
|
-
style: {
|
|
967
|
-
fg: "default",
|
|
968
|
-
border: { fg: "cyan" }
|
|
969
|
-
},
|
|
970
|
-
label: " Push to Remote "
|
|
971
|
-
});
|
|
972
|
-
blessed.text({
|
|
973
|
-
parent: form,
|
|
974
|
-
top: 1,
|
|
975
|
-
left: 2,
|
|
976
|
-
content: "Push branch to GitHub immediately?",
|
|
977
|
-
style: { fg: "default" }
|
|
978
|
-
});
|
|
979
|
-
const list = blessed.list({
|
|
980
|
-
parent: form,
|
|
981
|
-
top: 3,
|
|
982
|
-
left: 2,
|
|
983
|
-
width: 39,
|
|
984
|
-
height: 2,
|
|
985
|
-
keys: true,
|
|
986
|
-
vi: true,
|
|
987
|
-
style: {
|
|
988
|
-
selected: { bg: "cyan", fg: "black" },
|
|
989
|
-
item: { fg: "default" }
|
|
990
|
-
},
|
|
991
|
-
items: [" No (push later)", " Yes (visible on GitHub now)"]
|
|
992
|
-
});
|
|
993
|
-
list.focus();
|
|
994
|
-
screen.render();
|
|
995
|
-
list.on("select", (_item, index) => {
|
|
996
|
-
state.pushToRemote = index === 1;
|
|
997
|
-
form.destroy();
|
|
998
|
-
askAITool(state);
|
|
999
|
-
});
|
|
1000
|
-
list.key(["escape"], () => {
|
|
1001
|
-
form.destroy();
|
|
1002
|
-
screen.render();
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
function askAITool(state) {
|
|
800
|
+
function showAIToolSelector(branchName) {
|
|
1006
801
|
const form = blessed.box({
|
|
1007
802
|
parent: screen,
|
|
1008
803
|
top: "center",
|
|
1009
804
|
left: "center",
|
|
1010
805
|
width: 40,
|
|
1011
|
-
height:
|
|
806
|
+
height: 9,
|
|
1012
807
|
border: { type: "line" },
|
|
1013
|
-
style: {
|
|
1014
|
-
fg: "default",
|
|
1015
|
-
border: { fg: "cyan" }
|
|
1016
|
-
},
|
|
808
|
+
style: { fg: "default", border: { fg: "cyan" } },
|
|
1017
809
|
label: " Launch AI Tool "
|
|
1018
810
|
});
|
|
1019
811
|
blessed.text({
|
|
@@ -1035,60 +827,38 @@ function askAITool(state) {
|
|
|
1035
827
|
selected: { bg: "cyan", fg: "black" },
|
|
1036
828
|
item: { fg: "default" }
|
|
1037
829
|
},
|
|
1038
|
-
items: [" Claude Code", " Codex", " Skip
|
|
830
|
+
items: [" Claude Code", " Codex", " Skip"]
|
|
1039
831
|
});
|
|
1040
832
|
list.focus();
|
|
1041
833
|
screen.render();
|
|
1042
834
|
list.on("select", (_item, index) => {
|
|
1043
|
-
|
|
835
|
+
const tool = index === 0 ? "claude" : index === 1 ? "codex" : null;
|
|
1044
836
|
form.destroy();
|
|
1045
|
-
|
|
837
|
+
screen.render();
|
|
838
|
+
createNewWorktree(branchName, tool);
|
|
1046
839
|
});
|
|
1047
840
|
list.key(["escape"], () => {
|
|
1048
841
|
form.destroy();
|
|
1049
842
|
screen.render();
|
|
843
|
+
worktreeList.focus();
|
|
1050
844
|
});
|
|
1051
845
|
}
|
|
1052
|
-
async function
|
|
1053
|
-
const { branchName, baseBranch, copyEnv, pushToRemote, aiTool } = state;
|
|
846
|
+
async function createNewWorktree(branchName, tool) {
|
|
1054
847
|
setStatus(`Creating ${branchName}...`);
|
|
1055
848
|
try {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}
|
|
1065
|
-
if (pushToRemote) {
|
|
1066
|
-
setStatus(`Pushing ${branchName}...`);
|
|
1067
|
-
await pushBranch(branchName, worktreePath);
|
|
1068
|
-
}
|
|
1069
|
-
await refreshWorktrees();
|
|
1070
|
-
setStatus(`Created ${branchName}`);
|
|
1071
|
-
if (aiTool !== "skip") {
|
|
1072
|
-
await launchInWorktree(worktreePath, aiTool);
|
|
1073
|
-
}
|
|
1074
|
-
} else {
|
|
1075
|
-
const worktreePath = getWorktreePath(mainRepoPath, branchName);
|
|
1076
|
-
await createWorktree(worktreePath, branchName);
|
|
1077
|
-
if (copyEnv) {
|
|
1078
|
-
await copyEnvFiles(mainRepoPath, worktreePath);
|
|
1079
|
-
}
|
|
1080
|
-
if (pushToRemote) {
|
|
1081
|
-
setStatus(`Pushing ${branchName}...`);
|
|
1082
|
-
await pushBranch(branchName, worktreePath);
|
|
1083
|
-
}
|
|
1084
|
-
await refreshWorktrees();
|
|
1085
|
-
setStatus(`Created ${branchName}`);
|
|
1086
|
-
if (aiTool !== "skip") {
|
|
1087
|
-
await launchInWorktree(worktreePath, aiTool);
|
|
1088
|
-
}
|
|
849
|
+
const worktreePath = getWorktreePath(mainRepoPath, branchName);
|
|
850
|
+
await createWorktree(worktreePath, branchName);
|
|
851
|
+
await copyEnvFiles(mainRepoPath, worktreePath);
|
|
852
|
+
await refreshWorktrees();
|
|
853
|
+
setStatus(`Created ${branchName}`);
|
|
854
|
+
worktreeList.focus();
|
|
855
|
+
if (tool) {
|
|
856
|
+
await launchInWorktree(worktreePath, tool);
|
|
1089
857
|
}
|
|
1090
858
|
} catch (e) {
|
|
1091
859
|
setStatus(`Error: ${e.message}`);
|
|
860
|
+
worktreeList.focus();
|
|
861
|
+
screen.render();
|
|
1092
862
|
}
|
|
1093
863
|
}
|
|
1094
864
|
async function launchInWorktree(worktreePath, tool) {
|
|
@@ -1097,18 +867,42 @@ async function launchInWorktree(worktreePath, tool) {
|
|
|
1097
867
|
setStatus(`${tool} is not installed`);
|
|
1098
868
|
return;
|
|
1099
869
|
}
|
|
1100
|
-
|
|
1101
|
-
screen.program.clear();
|
|
1102
|
-
screen.program.disableMouse();
|
|
1103
|
-
screen.program.showCursor();
|
|
1104
|
-
screen.program.normalBuffer();
|
|
1105
|
-
screen.destroy();
|
|
870
|
+
cleanupScreen();
|
|
1106
871
|
launchAITool({ cwd: worktreePath, tool });
|
|
1107
872
|
console.log(`
|
|
1108
873
|
${tool} launched in: ${worktreePath}
|
|
1109
874
|
`);
|
|
1110
875
|
process.exit(0);
|
|
1111
876
|
}
|
|
877
|
+
async function launchTool(tool) {
|
|
878
|
+
const wt = worktrees[selectedIndex];
|
|
879
|
+
if (!wt) return;
|
|
880
|
+
const available = await isToolAvailable(tool);
|
|
881
|
+
if (!available) {
|
|
882
|
+
setStatus(`${tool} is not installed`);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
cleanupScreen();
|
|
886
|
+
launchAITool({ cwd: wt.path, tool });
|
|
887
|
+
console.log(`
|
|
888
|
+
${tool} launched in: ${path8.basename(wt.path)}
|
|
889
|
+
`);
|
|
890
|
+
process.exit(0);
|
|
891
|
+
}
|
|
892
|
+
async function pushSelected() {
|
|
893
|
+
const wt = worktrees[selectedIndex];
|
|
894
|
+
if (!wt?.branch) {
|
|
895
|
+
setStatus("No branch to push");
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
setStatus(`Pushing ${wt.branch}...`);
|
|
899
|
+
try {
|
|
900
|
+
await pushBranch(wt.branch, wt.path);
|
|
901
|
+
setStatus(`Pushed ${wt.branch} to origin`);
|
|
902
|
+
} catch (e) {
|
|
903
|
+
setStatus(`Error: ${e.message}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
1112
906
|
async function deleteSelected() {
|
|
1113
907
|
const wt = worktrees[selectedIndex];
|
|
1114
908
|
if (!wt) return;
|
|
@@ -1117,20 +911,17 @@ async function deleteSelected() {
|
|
|
1117
911
|
return;
|
|
1118
912
|
}
|
|
1119
913
|
const dirName = path8.basename(wt.path);
|
|
1120
|
-
const
|
|
914
|
+
const dialog = blessed.question({
|
|
1121
915
|
parent: screen,
|
|
1122
916
|
top: "center",
|
|
1123
917
|
left: "center",
|
|
1124
918
|
width: 40,
|
|
1125
919
|
height: 5,
|
|
1126
920
|
border: { type: "line" },
|
|
1127
|
-
style: {
|
|
1128
|
-
fg: "default",
|
|
1129
|
-
border: { fg: "red" }
|
|
1130
|
-
}
|
|
921
|
+
style: { fg: "default", border: { fg: "red" } }
|
|
1131
922
|
});
|
|
1132
|
-
|
|
1133
|
-
|
|
923
|
+
dialog.ask(`Delete ${dirName}?`, async (_err, yes) => {
|
|
924
|
+
dialog.destroy();
|
|
1134
925
|
if (yes) {
|
|
1135
926
|
setStatus(`Deleting ${dirName}...`);
|
|
1136
927
|
try {
|
|
@@ -1147,34 +938,15 @@ async function deleteSelected() {
|
|
|
1147
938
|
if (selectedIndex > 0) selectedIndex--;
|
|
1148
939
|
await refreshWorktrees();
|
|
1149
940
|
} else {
|
|
1150
|
-
|
|
941
|
+
screen.render();
|
|
942
|
+
worktreeList.focus();
|
|
1151
943
|
}
|
|
1152
944
|
});
|
|
1153
945
|
}
|
|
1154
|
-
async function launchAI(tool) {
|
|
1155
|
-
const wt = worktrees[selectedIndex];
|
|
1156
|
-
if (!wt) return;
|
|
1157
|
-
const available = await isToolAvailable(tool);
|
|
1158
|
-
if (!available) {
|
|
1159
|
-
setStatus(`${tool} is not installed`);
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
setStatus(`Launching ${tool}...`);
|
|
1163
|
-
screen.program.clear();
|
|
1164
|
-
screen.program.disableMouse();
|
|
1165
|
-
screen.program.showCursor();
|
|
1166
|
-
screen.program.normalBuffer();
|
|
1167
|
-
screen.destroy();
|
|
1168
|
-
launchAITool({ cwd: wt.path, tool });
|
|
1169
|
-
console.log(`
|
|
1170
|
-
${tool} launched in: ${path8.basename(wt.path)}
|
|
1171
|
-
`);
|
|
1172
|
-
process.exit(0);
|
|
1173
|
-
}
|
|
1174
946
|
|
|
1175
947
|
// src/index.ts
|
|
1176
948
|
var program = new Command();
|
|
1177
|
-
program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.
|
|
949
|
+
program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.5.0").action(async () => {
|
|
1178
950
|
await interactiveCommand();
|
|
1179
951
|
});
|
|
1180
952
|
program.command("new <branch-name>").description("Create a new worktree and launch AI assistant").option("-i, --install", "Run package manager install after creating worktree").option("-s, --skip-launch", "Create worktree without launching AI assistant").option("-p, --push", "Push branch to remote (visible on GitHub)").action(async (branchName, options) => {
|