worktree-launcher 1.4.2 → 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 +94 -339
- 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,15 @@ 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
834
|
list.on("select", (_item, index) => {
|
|
1054
|
-
|
|
835
|
+
const tool = index === 0 ? "claude" : index === 1 ? "codex" : null;
|
|
1055
836
|
form.destroy();
|
|
1056
837
|
screen.render();
|
|
1057
|
-
|
|
838
|
+
createNewWorktree(branchName, tool);
|
|
1058
839
|
});
|
|
1059
840
|
list.key(["escape"], () => {
|
|
1060
841
|
form.destroy();
|
|
@@ -1062,45 +843,17 @@ function askAITool(state) {
|
|
|
1062
843
|
worktreeList.focus();
|
|
1063
844
|
});
|
|
1064
845
|
}
|
|
1065
|
-
async function
|
|
1066
|
-
const { branchName, baseBranch, copyEnv, pushToRemote, aiTool } = state;
|
|
846
|
+
async function createNewWorktree(branchName, tool) {
|
|
1067
847
|
setStatus(`Creating ${branchName}...`);
|
|
1068
848
|
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
|
-
}
|
|
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);
|
|
1104
857
|
}
|
|
1105
858
|
} catch (e) {
|
|
1106
859
|
setStatus(`Error: ${e.message}`);
|
|
@@ -1114,18 +867,42 @@ async function launchInWorktree(worktreePath, tool) {
|
|
|
1114
867
|
setStatus(`${tool} is not installed`);
|
|
1115
868
|
return;
|
|
1116
869
|
}
|
|
1117
|
-
|
|
1118
|
-
screen.program.clear();
|
|
1119
|
-
screen.program.disableMouse();
|
|
1120
|
-
screen.program.showCursor();
|
|
1121
|
-
screen.program.normalBuffer();
|
|
1122
|
-
screen.destroy();
|
|
870
|
+
cleanupScreen();
|
|
1123
871
|
launchAITool({ cwd: worktreePath, tool });
|
|
1124
872
|
console.log(`
|
|
1125
873
|
${tool} launched in: ${worktreePath}
|
|
1126
874
|
`);
|
|
1127
875
|
process.exit(0);
|
|
1128
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
|
+
}
|
|
1129
906
|
async function deleteSelected() {
|
|
1130
907
|
const wt = worktrees[selectedIndex];
|
|
1131
908
|
if (!wt) return;
|
|
@@ -1134,20 +911,17 @@ async function deleteSelected() {
|
|
|
1134
911
|
return;
|
|
1135
912
|
}
|
|
1136
913
|
const dirName = path8.basename(wt.path);
|
|
1137
|
-
const
|
|
914
|
+
const dialog = blessed.question({
|
|
1138
915
|
parent: screen,
|
|
1139
916
|
top: "center",
|
|
1140
917
|
left: "center",
|
|
1141
918
|
width: 40,
|
|
1142
919
|
height: 5,
|
|
1143
920
|
border: { type: "line" },
|
|
1144
|
-
style: {
|
|
1145
|
-
fg: "default",
|
|
1146
|
-
border: { fg: "red" }
|
|
1147
|
-
}
|
|
921
|
+
style: { fg: "default", border: { fg: "red" } }
|
|
1148
922
|
});
|
|
1149
|
-
|
|
1150
|
-
|
|
923
|
+
dialog.ask(`Delete ${dirName}?`, async (_err, yes) => {
|
|
924
|
+
dialog.destroy();
|
|
1151
925
|
if (yes) {
|
|
1152
926
|
setStatus(`Deleting ${dirName}...`);
|
|
1153
927
|
try {
|
|
@@ -1164,34 +938,15 @@ async function deleteSelected() {
|
|
|
1164
938
|
if (selectedIndex > 0) selectedIndex--;
|
|
1165
939
|
await refreshWorktrees();
|
|
1166
940
|
} else {
|
|
1167
|
-
|
|
941
|
+
screen.render();
|
|
942
|
+
worktreeList.focus();
|
|
1168
943
|
}
|
|
1169
944
|
});
|
|
1170
945
|
}
|
|
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
946
|
|
|
1192
947
|
// src/index.ts
|
|
1193
948
|
var program = new Command();
|
|
1194
|
-
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 () => {
|
|
1195
950
|
await interactiveCommand();
|
|
1196
951
|
});
|
|
1197
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) => {
|