opencode-worktree 0.3.0 → 0.3.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 +30 -2
- package/package.json +1 -1
- package/src/config.ts +5 -0
- package/src/opencode.ts +20 -11
- package/src/ui.ts +99 -37
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ opencode-worktree /path/to/your/repo
|
|
|
43
43
|
|
|
44
44
|
- `Up`/`Down` or `j`/`k`: navigate
|
|
45
45
|
- `Enter`: open selected worktree in opencode (or toggle selection in delete mode)
|
|
46
|
-
- `o`: open worktree folder in file manager (
|
|
46
|
+
- `o`: open worktree folder in file manager or custom editor (configurable)
|
|
47
47
|
- `d`: enter multi-select delete mode (press again to confirm deletion)
|
|
48
48
|
- `n`: create new worktree
|
|
49
49
|
- `c`: edit configuration (post-create hooks)
|
|
@@ -67,7 +67,10 @@ When you first run `opencode-worktree` in a repository without a configuration f
|
|
|
67
67
|
|
|
68
68
|
### Editing configuration
|
|
69
69
|
|
|
70
|
-
Press `c` at any time to edit your configuration.
|
|
70
|
+
Press `c` at any time to edit your configuration. You can configure:
|
|
71
|
+
|
|
72
|
+
- **Post-create hook**: Command to run after creating a worktree (e.g., `npm install`)
|
|
73
|
+
- **Open command**: Custom command for opening worktree folders (e.g., `webstorm`, `code`)
|
|
71
74
|
|
|
72
75
|
### Post-create hooks
|
|
73
76
|
|
|
@@ -95,6 +98,31 @@ The hook output is streamed to the TUI in real-time. If the hook fails, you can
|
|
|
95
98
|
}
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
### Custom open command
|
|
102
|
+
|
|
103
|
+
Use a custom command when pressing `o` to open worktree folders. Useful for opening in your preferred IDE.
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"openCommand": "webstorm"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Examples:**
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"openCommand": "code"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"postCreateHook": "npm install",
|
|
122
|
+
"openCommand": "webstorm"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
98
126
|
## Update notifications
|
|
99
127
|
|
|
100
128
|
When a new version is published to npm, the CLI will show a non-intrusive update message on the next run.
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
export type Config = {
|
|
5
5
|
postCreateHook?: string;
|
|
6
|
+
openCommand?: string; // Custom command to open worktree folder (e.g., "webstorm", "code")
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
const CONFIG_FILENAME = ".opencode-worktree.json";
|
|
@@ -46,6 +47,10 @@ export const loadRepoConfig = (repoRoot: string): Config => {
|
|
|
46
47
|
config.postCreateHook = parsed.postCreateHook;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
if (typeof parsed.openCommand === "string") {
|
|
51
|
+
config.openCommand = parsed.openCommand;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
return config;
|
|
50
55
|
} catch {
|
|
51
56
|
// If we can't read or parse the config, return empty
|
package/src/opencode.ts
CHANGED
|
@@ -20,23 +20,32 @@ export const launchOpenCode = (cwd: string): void => {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Open a path in the system file manager
|
|
23
|
+
* Open a path in the system file manager or with a custom command
|
|
24
|
+
* If customCommand is provided, uses that instead of the system default
|
|
24
25
|
*/
|
|
25
|
-
export const openInFileManager = (path: string): boolean => {
|
|
26
|
-
const platform = process.platform;
|
|
26
|
+
export const openInFileManager = (path: string, customCommand?: string): boolean => {
|
|
27
27
|
let command: string;
|
|
28
28
|
let args: string[];
|
|
29
29
|
|
|
30
|
-
if (
|
|
31
|
-
command
|
|
32
|
-
|
|
33
|
-
} else if (platform === "win32") {
|
|
34
|
-
command = "explorer";
|
|
30
|
+
if (customCommand) {
|
|
31
|
+
// Use custom command (e.g., "webstorm", "code")
|
|
32
|
+
command = customCommand;
|
|
35
33
|
args = [path];
|
|
36
34
|
} else {
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// Use system default
|
|
36
|
+
const platform = process.platform;
|
|
37
|
+
|
|
38
|
+
if (platform === "darwin") {
|
|
39
|
+
command = "open";
|
|
40
|
+
args = [path];
|
|
41
|
+
} else if (platform === "win32") {
|
|
42
|
+
command = "explorer";
|
|
43
|
+
args = [path];
|
|
44
|
+
} else {
|
|
45
|
+
// Linux and others
|
|
46
|
+
command = "xdg-open";
|
|
47
|
+
args = [path];
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
try {
|
package/src/ui.ts
CHANGED
|
@@ -91,7 +91,9 @@ class WorktreeSelector {
|
|
|
91
91
|
// Config editor state
|
|
92
92
|
private isEditingConfig = false;
|
|
93
93
|
private configContainer: BoxRenderable | null = null;
|
|
94
|
-
private
|
|
94
|
+
private configHookInput: InputRenderable | null = null;
|
|
95
|
+
private configOpenInput: InputRenderable | null = null;
|
|
96
|
+
private configActiveField: "hook" | "open" = "hook";
|
|
95
97
|
private isFirstTimeSetup = false;
|
|
96
98
|
|
|
97
99
|
constructor(
|
|
@@ -263,8 +265,21 @@ class WorktreeSelector {
|
|
|
263
265
|
return;
|
|
264
266
|
}
|
|
265
267
|
if (key.name === "return") {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
+
this.handleConfigSave();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (key.name === "tab") {
|
|
272
|
+
// Switch between fields
|
|
273
|
+
if (this.configActiveField === "hook") {
|
|
274
|
+
this.configActiveField = "open";
|
|
275
|
+
this.configHookInput?.blur();
|
|
276
|
+
this.configOpenInput?.focus();
|
|
277
|
+
} else {
|
|
278
|
+
this.configActiveField = "hook";
|
|
279
|
+
this.configOpenInput?.blur();
|
|
280
|
+
this.configHookInput?.focus();
|
|
281
|
+
}
|
|
282
|
+
this.renderer.requestRender();
|
|
268
283
|
return;
|
|
269
284
|
}
|
|
270
285
|
return;
|
|
@@ -350,9 +365,17 @@ class WorktreeSelector {
|
|
|
350
365
|
return;
|
|
351
366
|
}
|
|
352
367
|
|
|
353
|
-
|
|
368
|
+
// Load config to check for custom open command
|
|
369
|
+
const config = this.repoRoot ? loadRepoConfig(this.repoRoot) : {};
|
|
370
|
+
const customCommand = config.openCommand;
|
|
371
|
+
|
|
372
|
+
const success = openInFileManager(worktree.path, customCommand);
|
|
354
373
|
if (success) {
|
|
355
|
-
|
|
374
|
+
if (customCommand) {
|
|
375
|
+
this.setStatus(`Opened ${worktree.path} with ${customCommand}.`, "success");
|
|
376
|
+
} else {
|
|
377
|
+
this.setStatus(`Opened ${worktree.path} in file manager.`, "success");
|
|
378
|
+
}
|
|
356
379
|
} else {
|
|
357
380
|
this.setStatus("Failed to open file manager.", "error");
|
|
358
381
|
}
|
|
@@ -658,6 +681,7 @@ class WorktreeSelector {
|
|
|
658
681
|
}
|
|
659
682
|
|
|
660
683
|
this.isEditingConfig = true;
|
|
684
|
+
this.configActiveField = "hook";
|
|
661
685
|
this.selectElement.visible = false;
|
|
662
686
|
this.selectElement.blur();
|
|
663
687
|
|
|
@@ -665,8 +689,8 @@ class WorktreeSelector {
|
|
|
665
689
|
const existingConfig = loadRepoConfig(this.repoRoot);
|
|
666
690
|
|
|
667
691
|
const title = this.isFirstTimeSetup
|
|
668
|
-
? "First-time Setup:
|
|
669
|
-
: "Edit
|
|
692
|
+
? "First-time Setup: Project Configuration"
|
|
693
|
+
: "Edit Project Configuration";
|
|
670
694
|
|
|
671
695
|
this.configContainer = new BoxRenderable(this.renderer, {
|
|
672
696
|
id: "config-container",
|
|
@@ -674,7 +698,7 @@ class WorktreeSelector {
|
|
|
674
698
|
left: 2,
|
|
675
699
|
top: 3,
|
|
676
700
|
width: 76,
|
|
677
|
-
height:
|
|
701
|
+
height: 12,
|
|
678
702
|
borderStyle: "single",
|
|
679
703
|
borderColor: "#38BDF8",
|
|
680
704
|
title,
|
|
@@ -684,50 +708,76 @@ class WorktreeSelector {
|
|
|
684
708
|
});
|
|
685
709
|
this.renderer.root.add(this.configContainer);
|
|
686
710
|
|
|
687
|
-
|
|
688
|
-
|
|
711
|
+
// Post-create hook field
|
|
712
|
+
const hookLabel = new TextRenderable(this.renderer, {
|
|
713
|
+
id: "config-hook-label",
|
|
689
714
|
position: "absolute",
|
|
690
715
|
left: 1,
|
|
691
716
|
top: 1,
|
|
692
|
-
content: "
|
|
717
|
+
content: "Post-create hook (e.g., npm install):",
|
|
693
718
|
fg: "#94A3B8",
|
|
694
719
|
});
|
|
695
|
-
this.configContainer.add(
|
|
720
|
+
this.configContainer.add(hookLabel);
|
|
696
721
|
|
|
697
|
-
|
|
698
|
-
id: "config-
|
|
722
|
+
this.configHookInput = new InputRenderable(this.renderer, {
|
|
723
|
+
id: "config-hook-input",
|
|
724
|
+
position: "absolute",
|
|
725
|
+
left: 1,
|
|
726
|
+
top: 2,
|
|
727
|
+
width: 72,
|
|
728
|
+
placeholder: "npm install",
|
|
729
|
+
value: existingConfig.postCreateHook || "",
|
|
730
|
+
focusedBackgroundColor: "#1E293B",
|
|
731
|
+
backgroundColor: "#1E293B",
|
|
732
|
+
});
|
|
733
|
+
this.configContainer.add(this.configHookInput);
|
|
734
|
+
|
|
735
|
+
// Open command field
|
|
736
|
+
const openLabel = new TextRenderable(this.renderer, {
|
|
737
|
+
id: "config-open-label",
|
|
699
738
|
position: "absolute",
|
|
700
739
|
left: 1,
|
|
701
740
|
top: 4,
|
|
702
|
-
content: "
|
|
703
|
-
fg: "#
|
|
741
|
+
content: "Open folder command (e.g., webstorm, code):",
|
|
742
|
+
fg: "#94A3B8",
|
|
704
743
|
});
|
|
705
|
-
this.configContainer.add(
|
|
744
|
+
this.configContainer.add(openLabel);
|
|
706
745
|
|
|
707
|
-
this.
|
|
708
|
-
id: "config-
|
|
746
|
+
this.configOpenInput = new InputRenderable(this.renderer, {
|
|
747
|
+
id: "config-open-input",
|
|
709
748
|
position: "absolute",
|
|
710
749
|
left: 1,
|
|
711
|
-
top:
|
|
750
|
+
top: 5,
|
|
712
751
|
width: 72,
|
|
713
|
-
placeholder: "
|
|
714
|
-
value: existingConfig.
|
|
752
|
+
placeholder: "open (default)",
|
|
753
|
+
value: existingConfig.openCommand || "",
|
|
715
754
|
focusedBackgroundColor: "#1E293B",
|
|
716
755
|
backgroundColor: "#1E293B",
|
|
717
756
|
});
|
|
718
|
-
this.configContainer.add(this.
|
|
757
|
+
this.configContainer.add(this.configOpenInput);
|
|
758
|
+
|
|
759
|
+
// Help text
|
|
760
|
+
const helpText = new TextRenderable(this.renderer, {
|
|
761
|
+
id: "config-help",
|
|
762
|
+
position: "absolute",
|
|
763
|
+
left: 1,
|
|
764
|
+
top: 7,
|
|
765
|
+
content: "Tab to switch fields • Leave empty to use defaults",
|
|
766
|
+
fg: "#64748B",
|
|
767
|
+
});
|
|
768
|
+
this.configContainer.add(helpText);
|
|
719
769
|
|
|
720
|
-
this.instructions.content = "Enter
|
|
770
|
+
this.instructions.content = "Tab switch • Enter save • Esc cancel";
|
|
721
771
|
this.setStatus(
|
|
722
772
|
this.isFirstTimeSetup
|
|
723
|
-
? "Welcome! Configure your
|
|
724
|
-
: "Edit
|
|
773
|
+
? "Welcome! Configure your project settings."
|
|
774
|
+
: "Edit project configuration.",
|
|
725
775
|
"info"
|
|
726
776
|
);
|
|
727
777
|
|
|
728
778
|
// Delay focus to prevent the triggering keypress from being captured
|
|
729
779
|
setTimeout(() => {
|
|
730
|
-
this.
|
|
780
|
+
this.configHookInput?.focus();
|
|
731
781
|
this.renderer.requestRender();
|
|
732
782
|
}, 0);
|
|
733
783
|
}
|
|
@@ -736,14 +786,18 @@ class WorktreeSelector {
|
|
|
736
786
|
this.isEditingConfig = false;
|
|
737
787
|
this.isFirstTimeSetup = false;
|
|
738
788
|
|
|
739
|
-
if (this.
|
|
740
|
-
this.
|
|
789
|
+
if (this.configHookInput) {
|
|
790
|
+
this.configHookInput.blur();
|
|
791
|
+
}
|
|
792
|
+
if (this.configOpenInput) {
|
|
793
|
+
this.configOpenInput.blur();
|
|
741
794
|
}
|
|
742
795
|
|
|
743
796
|
if (this.configContainer) {
|
|
744
797
|
this.renderer.root.remove(this.configContainer.id);
|
|
745
798
|
this.configContainer = null;
|
|
746
|
-
this.
|
|
799
|
+
this.configHookInput = null;
|
|
800
|
+
this.configOpenInput = null;
|
|
747
801
|
}
|
|
748
802
|
|
|
749
803
|
this.selectElement.visible = true;
|
|
@@ -757,27 +811,35 @@ class WorktreeSelector {
|
|
|
757
811
|
}, 0);
|
|
758
812
|
}
|
|
759
813
|
|
|
760
|
-
private handleConfigSave(
|
|
814
|
+
private handleConfigSave(): void {
|
|
761
815
|
if (!this.repoRoot) {
|
|
762
816
|
this.setStatus("No git repository found.", "error");
|
|
763
817
|
this.hideConfigEditor();
|
|
764
818
|
return;
|
|
765
819
|
}
|
|
766
820
|
|
|
767
|
-
const
|
|
821
|
+
const hookValue = (this.configHookInput?.value || "").trim();
|
|
822
|
+
const openValue = (this.configOpenInput?.value || "").trim();
|
|
768
823
|
const config: Config = {};
|
|
769
824
|
|
|
770
|
-
if (
|
|
771
|
-
config.postCreateHook =
|
|
825
|
+
if (hookValue) {
|
|
826
|
+
config.postCreateHook = hookValue;
|
|
827
|
+
}
|
|
828
|
+
if (openValue) {
|
|
829
|
+
config.openCommand = openValue;
|
|
772
830
|
}
|
|
773
831
|
|
|
774
832
|
const success = saveRepoConfig(this.repoRoot, config);
|
|
775
833
|
|
|
776
834
|
if (success) {
|
|
777
|
-
|
|
778
|
-
|
|
835
|
+
const changes: string[] = [];
|
|
836
|
+
if (hookValue) changes.push(`hook: "${hookValue}"`);
|
|
837
|
+
if (openValue) changes.push(`open: "${openValue}"`);
|
|
838
|
+
|
|
839
|
+
if (changes.length > 0) {
|
|
840
|
+
this.setStatus(`Config saved: ${changes.join(", ")}`, "success");
|
|
779
841
|
} else {
|
|
780
|
-
this.setStatus("
|
|
842
|
+
this.setStatus("Config cleared.", "success");
|
|
781
843
|
}
|
|
782
844
|
} else {
|
|
783
845
|
this.setStatus("Failed to save config.", "error");
|