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 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 (Finder/Explorer)
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. Currently, this allows you to set or modify the post-create hook command.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-worktree",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "TUI for managing git worktrees with opencode integration.",
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 (Finder on macOS, xdg-open on Linux, explorer on Windows)
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 (platform === "darwin") {
31
- command = "open";
32
- args = [path];
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
- // Linux and others
38
- command = "xdg-open";
39
- args = [path];
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 configInput: InputRenderable | null = null;
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
- const value = this.configInput?.value || "";
267
- this.handleConfigSave(value);
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
- const success = openInFileManager(worktree.path);
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
- this.setStatus(`Opened ${worktree.path} in file manager.`, "success");
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: Configure Post-create Hook"
669
- : "Edit Post-create Hook";
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: 8,
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
- const helpText = new TextRenderable(this.renderer, {
688
- id: "config-help",
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: "Command to run after creating a worktree (e.g., npm install):",
717
+ content: "Post-create hook (e.g., npm install):",
693
718
  fg: "#94A3B8",
694
719
  });
695
- this.configContainer.add(helpText);
720
+ this.configContainer.add(hookLabel);
696
721
 
697
- const skipHint = new TextRenderable(this.renderer, {
698
- id: "config-skip-hint",
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: "Leave empty to skip post-create hooks.",
703
- fg: "#64748B",
741
+ content: "Open folder command (e.g., webstorm, code):",
742
+ fg: "#94A3B8",
704
743
  });
705
- this.configContainer.add(skipHint);
744
+ this.configContainer.add(openLabel);
706
745
 
707
- this.configInput = new InputRenderable(this.renderer, {
708
- id: "config-hook-input",
746
+ this.configOpenInput = new InputRenderable(this.renderer, {
747
+ id: "config-open-input",
709
748
  position: "absolute",
710
749
  left: 1,
711
- top: 3,
750
+ top: 5,
712
751
  width: 72,
713
- placeholder: "npm install",
714
- value: existingConfig.postCreateHook || "",
752
+ placeholder: "open (default)",
753
+ value: existingConfig.openCommand || "",
715
754
  focusedBackgroundColor: "#1E293B",
716
755
  backgroundColor: "#1E293B",
717
756
  });
718
- this.configContainer.add(this.configInput);
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 to save • Esc to cancel";
770
+ this.instructions.content = "Tab switch • Enter save • Esc cancel";
721
771
  this.setStatus(
722
772
  this.isFirstTimeSetup
723
- ? "Welcome! Configure your post-create hook for this repository."
724
- : "Edit the post-create hook command.",
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.configInput?.focus();
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.configInput) {
740
- this.configInput.blur();
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.configInput = null;
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(hookCommand: string): void {
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 trimmed = hookCommand.trim();
821
+ const hookValue = (this.configHookInput?.value || "").trim();
822
+ const openValue = (this.configOpenInput?.value || "").trim();
768
823
  const config: Config = {};
769
824
 
770
- if (trimmed) {
771
- config.postCreateHook = trimmed;
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
- if (trimmed) {
778
- this.setStatus(`Post-create hook saved: "${trimmed}"`, "success");
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("Post-create hook cleared.", "success");
842
+ this.setStatus("Config cleared.", "success");
781
843
  }
782
844
  } else {
783
845
  this.setStatus("Failed to save config.", "error");