worktree-launcher 1.4.2 → 1.5.1
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 +105 -340
- 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,97 +700,70 @@ 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" }
|
|
@@ -829,19 +771,18 @@ function askBranchName(state) {
|
|
|
829
771
|
input.focus();
|
|
830
772
|
screen.render();
|
|
831
773
|
input.on("submit", () => {
|
|
832
|
-
const value = input.getValue();
|
|
833
|
-
if (!value
|
|
774
|
+
const value = input.getValue()?.trim();
|
|
775
|
+
if (!value) {
|
|
834
776
|
form.destroy();
|
|
835
777
|
screen.render();
|
|
836
778
|
worktreeList.focus();
|
|
837
779
|
return;
|
|
838
780
|
}
|
|
839
781
|
try {
|
|
840
|
-
validateBranchName(value
|
|
841
|
-
state.branchName = value.trim();
|
|
782
|
+
validateBranchName(value);
|
|
842
783
|
form.destroy();
|
|
843
784
|
screen.render();
|
|
844
|
-
|
|
785
|
+
showAIToolSelector(value);
|
|
845
786
|
} catch (e) {
|
|
846
787
|
setStatus(`Error: ${e.message}`);
|
|
847
788
|
input.clearValue();
|
|
@@ -856,175 +797,15 @@ function askBranchName(state) {
|
|
|
856
797
|
});
|
|
857
798
|
input.readInput();
|
|
858
799
|
}
|
|
859
|
-
function
|
|
860
|
-
const form = blessed.box({
|
|
861
|
-
parent: screen,
|
|
862
|
-
top: "center",
|
|
863
|
-
left: "center",
|
|
864
|
-
width: 50,
|
|
865
|
-
height: 10,
|
|
866
|
-
border: { type: "line" },
|
|
867
|
-
style: {
|
|
868
|
-
fg: "default",
|
|
869
|
-
border: { fg: "cyan" }
|
|
870
|
-
},
|
|
871
|
-
label: " Base Branch "
|
|
872
|
-
});
|
|
873
|
-
blessed.text({
|
|
874
|
-
parent: form,
|
|
875
|
-
top: 1,
|
|
876
|
-
left: 2,
|
|
877
|
-
content: "Create worktree from:",
|
|
878
|
-
style: { fg: "default" }
|
|
879
|
-
});
|
|
880
|
-
const list = blessed.list({
|
|
881
|
-
parent: form,
|
|
882
|
-
top: 3,
|
|
883
|
-
left: 2,
|
|
884
|
-
width: 44,
|
|
885
|
-
height: 3,
|
|
886
|
-
keys: true,
|
|
887
|
-
vi: true,
|
|
888
|
-
style: {
|
|
889
|
-
selected: { bg: "cyan", fg: "black" },
|
|
890
|
-
item: { fg: "default" }
|
|
891
|
-
},
|
|
892
|
-
items: [
|
|
893
|
-
` Current branch (${currentBranch})`,
|
|
894
|
-
` Default branch (${defaultBranch})`
|
|
895
|
-
]
|
|
896
|
-
});
|
|
897
|
-
blessed.text({
|
|
898
|
-
parent: form,
|
|
899
|
-
top: 7,
|
|
900
|
-
left: 2,
|
|
901
|
-
content: "[Enter] select [Esc] cancel",
|
|
902
|
-
style: { fg: "cyan" }
|
|
903
|
-
});
|
|
904
|
-
list.focus();
|
|
905
|
-
screen.render();
|
|
906
|
-
list.on("select", (_item, index) => {
|
|
907
|
-
state.baseBranch = index === 0 ? "current" : "default";
|
|
908
|
-
form.destroy();
|
|
909
|
-
screen.render();
|
|
910
|
-
askCopyEnv(state);
|
|
911
|
-
});
|
|
912
|
-
list.key(["escape"], () => {
|
|
913
|
-
form.destroy();
|
|
914
|
-
screen.render();
|
|
915
|
-
worktreeList.focus();
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
function askCopyEnv(state) {
|
|
800
|
+
function showAIToolSelector(branchName) {
|
|
919
801
|
const form = blessed.box({
|
|
920
802
|
parent: screen,
|
|
921
803
|
top: "center",
|
|
922
804
|
left: "center",
|
|
923
805
|
width: 40,
|
|
924
|
-
height:
|
|
806
|
+
height: 9,
|
|
925
807
|
border: { type: "line" },
|
|
926
|
-
style: {
|
|
927
|
-
fg: "default",
|
|
928
|
-
border: { fg: "cyan" }
|
|
929
|
-
},
|
|
930
|
-
label: " Environment Files "
|
|
931
|
-
});
|
|
932
|
-
blessed.text({
|
|
933
|
-
parent: form,
|
|
934
|
-
top: 1,
|
|
935
|
-
left: 2,
|
|
936
|
-
content: "Copy .env files to worktree?",
|
|
937
|
-
style: { fg: "default" }
|
|
938
|
-
});
|
|
939
|
-
const list = blessed.list({
|
|
940
|
-
parent: form,
|
|
941
|
-
top: 3,
|
|
942
|
-
left: 2,
|
|
943
|
-
width: 34,
|
|
944
|
-
height: 2,
|
|
945
|
-
keys: true,
|
|
946
|
-
vi: true,
|
|
947
|
-
style: {
|
|
948
|
-
selected: { bg: "cyan", fg: "black" },
|
|
949
|
-
item: { fg: "default" }
|
|
950
|
-
},
|
|
951
|
-
items: [" Yes (recommended)", " No"]
|
|
952
|
-
});
|
|
953
|
-
list.focus();
|
|
954
|
-
screen.render();
|
|
955
|
-
list.on("select", (_item, index) => {
|
|
956
|
-
state.copyEnv = index === 0;
|
|
957
|
-
form.destroy();
|
|
958
|
-
screen.render();
|
|
959
|
-
askPushToRemote(state);
|
|
960
|
-
});
|
|
961
|
-
list.key(["escape"], () => {
|
|
962
|
-
form.destroy();
|
|
963
|
-
screen.render();
|
|
964
|
-
worktreeList.focus();
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
function askPushToRemote(state) {
|
|
968
|
-
const form = blessed.box({
|
|
969
|
-
parent: screen,
|
|
970
|
-
top: "center",
|
|
971
|
-
left: "center",
|
|
972
|
-
width: 45,
|
|
973
|
-
height: 8,
|
|
974
|
-
border: { type: "line" },
|
|
975
|
-
style: {
|
|
976
|
-
fg: "default",
|
|
977
|
-
border: { fg: "cyan" }
|
|
978
|
-
},
|
|
979
|
-
label: " Push to Remote "
|
|
980
|
-
});
|
|
981
|
-
blessed.text({
|
|
982
|
-
parent: form,
|
|
983
|
-
top: 1,
|
|
984
|
-
left: 2,
|
|
985
|
-
content: "Push branch to GitHub immediately?",
|
|
986
|
-
style: { fg: "default" }
|
|
987
|
-
});
|
|
988
|
-
const list = blessed.list({
|
|
989
|
-
parent: form,
|
|
990
|
-
top: 3,
|
|
991
|
-
left: 2,
|
|
992
|
-
width: 39,
|
|
993
|
-
height: 2,
|
|
994
|
-
keys: true,
|
|
995
|
-
vi: true,
|
|
996
|
-
style: {
|
|
997
|
-
selected: { bg: "cyan", fg: "black" },
|
|
998
|
-
item: { fg: "default" }
|
|
999
|
-
},
|
|
1000
|
-
items: [" No (push later)", " Yes (visible on GitHub now)"]
|
|
1001
|
-
});
|
|
1002
|
-
list.focus();
|
|
1003
|
-
screen.render();
|
|
1004
|
-
list.on("select", (_item, index) => {
|
|
1005
|
-
state.pushToRemote = index === 1;
|
|
1006
|
-
form.destroy();
|
|
1007
|
-
screen.render();
|
|
1008
|
-
askAITool(state);
|
|
1009
|
-
});
|
|
1010
|
-
list.key(["escape"], () => {
|
|
1011
|
-
form.destroy();
|
|
1012
|
-
screen.render();
|
|
1013
|
-
worktreeList.focus();
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
function askAITool(state) {
|
|
1017
|
-
const form = blessed.box({
|
|
1018
|
-
parent: screen,
|
|
1019
|
-
top: "center",
|
|
1020
|
-
left: "center",
|
|
1021
|
-
width: 40,
|
|
1022
|
-
height: 10,
|
|
1023
|
-
border: { type: "line" },
|
|
1024
|
-
style: {
|
|
1025
|
-
fg: "default",
|
|
1026
|
-
border: { fg: "cyan" }
|
|
1027
|
-
},
|
|
808
|
+
style: { fg: "default", border: { fg: "cyan" } },
|
|
1028
809
|
label: " Launch AI Tool "
|
|
1029
810
|
});
|
|
1030
811
|
blessed.text({
|
|
@@ -1046,15 +827,25 @@ function askAITool(state) {
|
|
|
1046
827
|
selected: { bg: "cyan", fg: "black" },
|
|
1047
828
|
item: { fg: "default" }
|
|
1048
829
|
},
|
|
1049
|
-
items: [" Claude Code", " Codex", " Skip
|
|
830
|
+
items: [" Claude Code", " Codex", " Skip"]
|
|
1050
831
|
});
|
|
1051
832
|
list.focus();
|
|
1052
833
|
screen.render();
|
|
1053
|
-
list.on("select", (_item, index) => {
|
|
1054
|
-
|
|
834
|
+
list.on("select", async (_item, index) => {
|
|
835
|
+
const tool = index === 0 ? "claude" : index === 1 ? "codex" : null;
|
|
1055
836
|
form.destroy();
|
|
837
|
+
setStatus(`Selected: ${tool ?? "skip"}, creating worktree...`);
|
|
1056
838
|
screen.render();
|
|
1057
|
-
|
|
839
|
+
try {
|
|
840
|
+
await createNewWorktree(branchName, tool);
|
|
841
|
+
} catch (e) {
|
|
842
|
+
setStatus(`Error: ${e.message}`);
|
|
843
|
+
worktreeList.focus();
|
|
844
|
+
screen.render();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
list.key(["enter"], () => {
|
|
848
|
+
list.emit("select", list.items[list.selected], list.selected);
|
|
1058
849
|
});
|
|
1059
850
|
list.key(["escape"], () => {
|
|
1060
851
|
form.destroy();
|
|
@@ -1062,45 +853,17 @@ function askAITool(state) {
|
|
|
1062
853
|
worktreeList.focus();
|
|
1063
854
|
});
|
|
1064
855
|
}
|
|
1065
|
-
async function
|
|
1066
|
-
const { branchName, baseBranch, copyEnv, pushToRemote, aiTool } = state;
|
|
856
|
+
async function createNewWorktree(branchName, tool) {
|
|
1067
857
|
setStatus(`Creating ${branchName}...`);
|
|
1068
858
|
try {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
if (pushToRemote) {
|
|
1079
|
-
setStatus(`Pushing ${branchName}...`);
|
|
1080
|
-
await pushBranch(branchName, worktreePath);
|
|
1081
|
-
}
|
|
1082
|
-
await refreshWorktrees();
|
|
1083
|
-
setStatus(`Created ${branchName}`);
|
|
1084
|
-
worktreeList.focus();
|
|
1085
|
-
if (aiTool !== "skip") {
|
|
1086
|
-
await launchInWorktree(worktreePath, aiTool);
|
|
1087
|
-
}
|
|
1088
|
-
} else {
|
|
1089
|
-
const worktreePath = getWorktreePath(mainRepoPath, branchName);
|
|
1090
|
-
await createWorktree(worktreePath, branchName);
|
|
1091
|
-
if (copyEnv) {
|
|
1092
|
-
await copyEnvFiles(mainRepoPath, worktreePath);
|
|
1093
|
-
}
|
|
1094
|
-
if (pushToRemote) {
|
|
1095
|
-
setStatus(`Pushing ${branchName}...`);
|
|
1096
|
-
await pushBranch(branchName, worktreePath);
|
|
1097
|
-
}
|
|
1098
|
-
await refreshWorktrees();
|
|
1099
|
-
setStatus(`Created ${branchName}`);
|
|
1100
|
-
worktreeList.focus();
|
|
1101
|
-
if (aiTool !== "skip") {
|
|
1102
|
-
await launchInWorktree(worktreePath, aiTool);
|
|
1103
|
-
}
|
|
859
|
+
const worktreePath = getWorktreePath(mainRepoPath, branchName);
|
|
860
|
+
await createWorktree(worktreePath, branchName);
|
|
861
|
+
await copyEnvFiles(mainRepoPath, worktreePath);
|
|
862
|
+
await refreshWorktrees();
|
|
863
|
+
setStatus(`Created ${branchName}`);
|
|
864
|
+
worktreeList.focus();
|
|
865
|
+
if (tool) {
|
|
866
|
+
await launchInWorktree(worktreePath, tool);
|
|
1104
867
|
}
|
|
1105
868
|
} catch (e) {
|
|
1106
869
|
setStatus(`Error: ${e.message}`);
|
|
@@ -1114,18 +877,42 @@ async function launchInWorktree(worktreePath, tool) {
|
|
|
1114
877
|
setStatus(`${tool} is not installed`);
|
|
1115
878
|
return;
|
|
1116
879
|
}
|
|
1117
|
-
|
|
1118
|
-
screen.program.clear();
|
|
1119
|
-
screen.program.disableMouse();
|
|
1120
|
-
screen.program.showCursor();
|
|
1121
|
-
screen.program.normalBuffer();
|
|
1122
|
-
screen.destroy();
|
|
880
|
+
cleanupScreen();
|
|
1123
881
|
launchAITool({ cwd: worktreePath, tool });
|
|
1124
882
|
console.log(`
|
|
1125
883
|
${tool} launched in: ${worktreePath}
|
|
1126
884
|
`);
|
|
1127
885
|
process.exit(0);
|
|
1128
886
|
}
|
|
887
|
+
async function launchTool(tool) {
|
|
888
|
+
const wt = worktrees[selectedIndex];
|
|
889
|
+
if (!wt) return;
|
|
890
|
+
const available = await isToolAvailable(tool);
|
|
891
|
+
if (!available) {
|
|
892
|
+
setStatus(`${tool} is not installed`);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
cleanupScreen();
|
|
896
|
+
launchAITool({ cwd: wt.path, tool });
|
|
897
|
+
console.log(`
|
|
898
|
+
${tool} launched in: ${path8.basename(wt.path)}
|
|
899
|
+
`);
|
|
900
|
+
process.exit(0);
|
|
901
|
+
}
|
|
902
|
+
async function pushSelected() {
|
|
903
|
+
const wt = worktrees[selectedIndex];
|
|
904
|
+
if (!wt?.branch) {
|
|
905
|
+
setStatus("No branch to push");
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
setStatus(`Pushing ${wt.branch}...`);
|
|
909
|
+
try {
|
|
910
|
+
await pushBranch(wt.branch, wt.path);
|
|
911
|
+
setStatus(`Pushed ${wt.branch} to origin`);
|
|
912
|
+
} catch (e) {
|
|
913
|
+
setStatus(`Error: ${e.message}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
1129
916
|
async function deleteSelected() {
|
|
1130
917
|
const wt = worktrees[selectedIndex];
|
|
1131
918
|
if (!wt) return;
|
|
@@ -1134,20 +921,17 @@ async function deleteSelected() {
|
|
|
1134
921
|
return;
|
|
1135
922
|
}
|
|
1136
923
|
const dirName = path8.basename(wt.path);
|
|
1137
|
-
const
|
|
924
|
+
const dialog = blessed.question({
|
|
1138
925
|
parent: screen,
|
|
1139
926
|
top: "center",
|
|
1140
927
|
left: "center",
|
|
1141
928
|
width: 40,
|
|
1142
929
|
height: 5,
|
|
1143
930
|
border: { type: "line" },
|
|
1144
|
-
style: {
|
|
1145
|
-
fg: "default",
|
|
1146
|
-
border: { fg: "red" }
|
|
1147
|
-
}
|
|
931
|
+
style: { fg: "default", border: { fg: "red" } }
|
|
1148
932
|
});
|
|
1149
|
-
|
|
1150
|
-
|
|
933
|
+
dialog.ask(`Delete ${dirName}?`, async (_err, yes) => {
|
|
934
|
+
dialog.destroy();
|
|
1151
935
|
if (yes) {
|
|
1152
936
|
setStatus(`Deleting ${dirName}...`);
|
|
1153
937
|
try {
|
|
@@ -1164,34 +948,15 @@ async function deleteSelected() {
|
|
|
1164
948
|
if (selectedIndex > 0) selectedIndex--;
|
|
1165
949
|
await refreshWorktrees();
|
|
1166
950
|
} else {
|
|
1167
|
-
|
|
951
|
+
screen.render();
|
|
952
|
+
worktreeList.focus();
|
|
1168
953
|
}
|
|
1169
954
|
});
|
|
1170
955
|
}
|
|
1171
|
-
async function launchAI(tool) {
|
|
1172
|
-
const wt = worktrees[selectedIndex];
|
|
1173
|
-
if (!wt) return;
|
|
1174
|
-
const available = await isToolAvailable(tool);
|
|
1175
|
-
if (!available) {
|
|
1176
|
-
setStatus(`${tool} is not installed`);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
setStatus(`Launching ${tool}...`);
|
|
1180
|
-
screen.program.clear();
|
|
1181
|
-
screen.program.disableMouse();
|
|
1182
|
-
screen.program.showCursor();
|
|
1183
|
-
screen.program.normalBuffer();
|
|
1184
|
-
screen.destroy();
|
|
1185
|
-
launchAITool({ cwd: wt.path, tool });
|
|
1186
|
-
console.log(`
|
|
1187
|
-
${tool} launched in: ${path8.basename(wt.path)}
|
|
1188
|
-
`);
|
|
1189
|
-
process.exit(0);
|
|
1190
|
-
}
|
|
1191
956
|
|
|
1192
957
|
// src/index.ts
|
|
1193
958
|
var program = new Command();
|
|
1194
|
-
program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.
|
|
959
|
+
program.name("wt").description("CLI tool to streamline git worktrees with AI coding assistants").version("1.5.1").action(async () => {
|
|
1195
960
|
await interactiveCommand();
|
|
1196
961
|
});
|
|
1197
962
|
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) => {
|