workshell 0.0.2 → 0.0.3

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.
Files changed (3) hide show
  1. package/README.md +41 -41
  2. package/dist/index.js +83 -103
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -27,16 +27,16 @@ $ npm i -g workshell
27
27
 
28
28
  ## How `workshell` works
29
29
 
30
- There have been many attempts to nail a DX for parallel work in the agentic coding era. Most are thin wrappers over `git worktree`. Instead `wsh` introduces a new paradigm: the *workshell*.
30
+ There have been many attempts to nail a DX for parallel work in the agentic coding era. Most are thin wrappers over `git worktree`. Instead `wksh` introduces a new paradigm: the *workshell*.
31
31
 
32
32
  **A _workshell_ is an ephemeral worktree whose lifecycle is bound to a subshell.**
33
33
 
34
34
  Here's how it works (key points in **bold**).
35
35
 
36
- - You open a Git branch with `wsh open <branch>` or create a new one with `wsh new <branch>`.
37
- - An ephemeral worktree is created for this branch (in `.git/wsh/worktrees`) and **opened in a fresh subshell**.
36
+ - You open a Git branch with `wksh open <branch>` or create a new one with `wksh new <branch>`.
37
+ - An ephemeral worktree is created for this branch (in `.git/workshell/worktrees`) and **opened in a fresh subshell**.
38
38
  - You are now in an fresh checkout of your repo that is isolated on disk. Make changes with your agent/editor of choice and commit them.
39
- - You close the subshell with `wsh close`. **The associated worktree is auto-pruned**.
39
+ - You close the subshell with `wksh close`. **The associated worktree is auto-pruned**.
40
40
  - Your changes still exist on the associated branch, as Git commits/branches are shared among all worktrees. The worktree is destroyed—but your commits aren't.
41
41
 
42
42
  <!-- That's it. **Ephemeral worktrees whose lifecycle is bound to a subshell.** When the subshell exits, the worktree is destroyed—but your commits aren't. -->
@@ -44,14 +44,14 @@ Here's how it works (key points in **bold**).
44
44
  <!--
45
45
  ## How does it work
46
46
 
47
- There have been many attempts to nail a DX for parallel work in the agentic coding era. Most are thin wrappers over `git worktree`. That's not what `wsh` is (though worktrees are used internally).
47
+ There have been many attempts to nail a DX for parallel work in the agentic coding era. Most are thin wrappers over `git worktree`. That's not what `wksh` is (though worktrees are used internally).
48
48
 
49
49
  Here's how it works (key points in **bold**).
50
50
 
51
- - **A fresh worktree is created** — The `wsh` utility a) creates a new worktree in `.git/wsh/worktrees` and b) opens it in a *subshell*.
51
+ - **A fresh worktree is created** — The `wksh` utility a) creates a new worktree in `.git/workshell/worktrees` and b) opens it in a *subshell*.
52
52
  - **Make edits and commit** — From the worksh your preferred IDE/agent.
53
53
  - **Commit your changes** — Though your file system is isolated, Git commit history (including branches) is still shared among all worktrees.
54
- - **Exit the subshell** — Run `wsh close` to exit the subshell. As with `git switch`, `wsh close` won't let you close the subshell if you have unstaged/uncommitted changes.
54
+ - **Exit the subshell** — Run `wksh close` to exit the subshell. As with `git switch`, `wksh close` won't let you close the subshell if you have unstaged/uncommitted changes.
55
55
  - **The worktree is auto-pruned** — This is key. The lifecycle of the worktree is tied to the subshell. When the subshell is closed, the worktree is destroyed. But *the commits you made inside the subshell* still exist.
56
56
  - **Merge in your changes** — Merge/rebase your branch as you normally would, or push to GitHub to open a PR. -->
57
57
 
@@ -63,15 +63,15 @@ This approach has some nice properties.
63
63
 
64
64
  - 🖥️ **Tab-local workspaces** — Normally a `git checkout`/`git switch` changes your active branch for all terminals. With workshells, you can functionality open branches *in the current tab only*.
65
65
  - 🌳 **Full isolation** — Each workshell is isolated on disk, so the changes you make don't interfere anything else you're doing.
66
- - 🙅‍♂️ **Never stash again** — You can `wsh open` a branch even with uncommitted changes. When you exit the subshell, things will be exactly the same as they were. ☕️
67
- - 🪾 **Consistent with branch semantics** — As with regular `git switch`, `wsh close` won't let you close the subshell if you have unstaged/uncommitted changes. This is a feature, not a bug! Regular worktrees make it easy to lose your work in a forgotten corner of your file system.
66
+ - 🙅‍♂️ **Never stash again** — You can `wksh open` a branch even with uncommitted changes. When you exit the subshell, things will be exactly the same as they were. ☕️
67
+ - 🪾 **Consistent with branch semantics** — As with regular `git switch`, `wksh close` won't let you close the subshell if you have unstaged/uncommitted changes. This is a feature, not a bug! Regular worktrees make it easy to lose your work in a forgotten corner of your file system.
68
68
  - 🤖 **Agent-ready** — Spin up parallel workshells so multiple agents can work simultaneously without conflicts.
69
69
 
70
70
  <br/>
71
71
 
72
72
  ## Quickstart
73
73
 
74
- This section is entirely linear and self-contained. Try running all these commands in order to get a feel for how `wsh` works. First, install `wsh`.
74
+ This section is entirely linear and self-contained. Try running all these commands in order to get a feel for how `wksh` works. First, install `wksh`.
75
75
 
76
76
  ```bash
77
77
  $ npm i -g workshell
@@ -91,11 +91,11 @@ $ cd zod
91
91
  After cloning, the `main` branch is checked out. Let's say we want to start work on a new feature:
92
92
 
93
93
  ```bash
94
- $ wsh new feat-1
94
+ $ wksh new feat-1
95
95
 
96
96
  ✓ feat-1 (from main)
97
- Opened branch in ephemeral subshell at .git/wsh/worktrees/zod@feat-1
98
- Type 'wsh close' to return.
97
+ Opened branch in ephemeral subshell at .git/workshell/worktrees/zod@feat-1
98
+ Type 'wksh close' to return.
99
99
  ```
100
100
 
101
101
  <br/>
@@ -104,7 +104,7 @@ You're now in a workshell. Check where you are:
104
104
 
105
105
  ```bash
106
106
  $ pwd
107
- /Users/colinmcd94/Documents/repos/zod/.git/wsh/worktrees/zod@feat-1
107
+ /Users/colinmcd94/Documents/repos/zod/.git/workshell/worktrees/zod@feat-1
108
108
 
109
109
  $ git branch --show-current
110
110
  feat-1
@@ -123,10 +123,10 @@ $ touch a.txt
123
123
  Now let's try to close the workshell.
124
124
 
125
125
  ```bash
126
- $ wsh close
126
+ $ wksh close
127
127
  ⚠ Uncommitted changes found. Commit, stash, or reset your changes first.
128
128
  Or use --force/-f to discard changes
129
- wsh close -f
129
+ wksh close -f
130
130
  ```
131
131
 
132
132
  <br/>
@@ -139,10 +139,10 @@ $ git add -A && git commit -am "Add a.txt"
139
139
 
140
140
  <br/>
141
141
 
142
- Now we can try closing again. Since our changes can be fast-forwarded from the base branch, `wsh` offers to auto-merge the changes.
142
+ Now we can try closing again. Since our changes can be fast-forwarded from the base branch, `wksh` offers to auto-merge the changes.
143
143
 
144
144
  ```bash
145
- $ wsh close
145
+ $ wksh close
146
146
  ✓ Back in main
147
147
  Pruned worktree. Your changes are still in the 'feat-1' branch.
148
148
  To merge your changes:
@@ -158,9 +158,9 @@ $ wsh close
158
158
  ## CLI
159
159
 
160
160
  ```sh
161
- wsh v0.x.y - Human- and agent-friendly Git multitasking
161
+ wksh v0.x.y - Human- and agent-friendly Git multitasking
162
162
 
163
- Usage: wsh <command> [options]
163
+ Usage: wksh <command> [options]
164
164
 
165
165
  Commands:
166
166
  new [branch] Create a branch and open it [--from <branch>]
@@ -180,10 +180,10 @@ Options:
180
180
 
181
181
  ### List orphaned worktrees
182
182
 
183
- Normally the worktree will be auto-pruned when you close its associated workshell. If a worktree is left behind for some reason, you can list them with `wsh ls`.
183
+ Normally the worktree will be auto-pruned when you close its associated workshell. If a worktree is left behind for some reason, you can list them with `wksh ls`.
184
184
 
185
185
  ```sh
186
- $ wsh ls
186
+ $ wksh ls
187
187
  ┌────────┬───────────┬───────────────┐
188
188
  │ branch │ status │ created │
189
189
  ├────────┼───────────┼───────────────┤
@@ -200,10 +200,10 @@ $ wsh ls
200
200
  You can open any existing Git branch in a workshell.
201
201
 
202
202
  ```sh
203
- $ wsh open feat-1
203
+ $ wksh open feat-1
204
204
 
205
205
  ✓ feat-1 (existing worktree)
206
- Type 'wsh close' to return.
206
+ Type 'wksh close' to return.
207
207
  ```
208
208
 
209
209
  <br />
@@ -211,7 +211,7 @@ Type 'wsh close' to return.
211
211
  ### Show current branch status
212
212
 
213
213
  ```sh
214
- $ wsh status
214
+ $ wksh status
215
215
  branch: main (root)
216
216
  worktree: /path/to/zod
217
217
  status: clean
@@ -224,7 +224,7 @@ status: clean
224
224
  Remove the worktree for a branch (the branch itself is kept):
225
225
 
226
226
  ```sh
227
- $ wsh rm feat-1
227
+ $ wksh rm feat-1
228
228
 
229
229
  ✓ Pruned worktree for feat-1
230
230
  ```
@@ -236,7 +236,7 @@ $ wsh rm feat-1
236
236
  This closes the current workshell and auto-prunes the associated worktree. Your changes survive in your branch commits.
237
237
 
238
238
  ```sh
239
- $ wsh close
239
+ $ wksh close
240
240
  ✓ Back in main
241
241
  Pruned worktree. Your changes are still in the 'feat-1' branch.
242
242
  To merge your changes:
@@ -256,19 +256,19 @@ If the branch hasn't been pushed to a remote, you'll be prompted to auto-merge:
256
256
  That command will fail if you have unstaged/uncommited changes. Use the `--force`/`-f` flag to force close the workshell; **this will discard uncommitted changes**.
257
257
 
258
258
  ```sh
259
- $ wsh close --force
259
+ $ wksh close --force
260
260
  ```
261
261
 
262
262
  <br />
263
263
 
264
- ### Print or create a `wsh.toml`
264
+ ### Print or create a `workshell.toml`
265
265
 
266
266
  To print or create a config file:
267
267
 
268
268
  ```sh
269
- $ wsh config
269
+ $ wksh config
270
270
 
271
- ✓ Config file: /path/to/repo/.git/wsh.toml
271
+ ✓ Config file: /path/to/repo/.git/workshell.toml
272
272
 
273
273
  ────────────────────────────────────────────────────────────
274
274
  setup = "npm install"
@@ -278,31 +278,31 @@ setup = "npm install"
278
278
  If no config exists, you'll be prompted to create one.
279
279
 
280
280
  ```sh
281
- $ wsh config
281
+ $ wksh config
282
282
 
283
283
  No config file found.
284
284
 
285
285
  Where would you like to create one?
286
- 1. .git/wsh.toml (local only, not committed)
287
- 2. wsh.toml (project root, can be committed)
286
+ 1. .git/workshell.toml (local only, not committed)
287
+ 2. workshell.toml (project root, can be committed)
288
288
 
289
289
  Choice (1/2): 1
290
290
 
291
- ✓ Created /path/to/repo/.git/wsh.toml
291
+ ✓ Created /path/to/repo/.git/workshell.toml
292
292
  ```
293
293
 
294
294
  <br />
295
295
 
296
- ## `wsh.toml`
296
+ ## `workshell.toml`
297
297
 
298
- You can configure `wsh` with a `wsh.toml` file. This is useful for running setup scripts when opening a workshell (e.g., `npm install`).
298
+ You can configure `wksh` with a `workshell.toml` file. This is useful for running setup scripts when opening a workshell (e.g., `npm install`).
299
299
 
300
- `wsh` looks for config files in the following order:
300
+ `wksh` looks for config files in the following order:
301
301
 
302
302
  | Path | Description |
303
303
  |------|-------------|
304
- | `.git/wsh.toml` | Local only, not committed (highest precedence) |
305
- | `wsh.toml` | Project root, can be committed |
304
+ | `.git/workshell.toml` | Local only, not committed (highest precedence) |
305
+ | `workshell.toml` | Project root, can be committed |
306
306
 
307
307
  <br />
308
308
 
@@ -317,5 +317,5 @@ The following variable substitutions are supported in `setup`.
317
317
  |----------|-------------|---------|
318
318
  | `{{ branch }}` | Branch name | `feature/auth` |
319
319
  | `{{ repo_path }}` | Absolute path to main repo | `/path/to/repo` |
320
- | `{{ worktree_path }}` | Absolute path to worktree | `/path/to/repo/.git/wsh/worktrees/repo@feat` |
320
+ | `{{ worktree_path }}` | Absolute path to worktree | `/path/to/repo/.git/workshell/worktrees/repo@feat` |
321
321
 
package/dist/index.js CHANGED
@@ -6494,10 +6494,10 @@ function getMainWorktree() {
6494
6494
  return dirname(getGitCommonDir());
6495
6495
  }
6496
6496
  function getWorktreesDir() {
6497
- return join(getGitCommonDir(), "wsh", "worktrees");
6497
+ return join(getGitCommonDir(), "workshell", "worktrees");
6498
6498
  }
6499
6499
  function getStorePath() {
6500
- return join(getGitCommonDir(), "wsh");
6500
+ return join(getGitCommonDir(), "workshell");
6501
6501
  }
6502
6502
  function loadStore() {
6503
6503
  const storePath = getStorePath();
@@ -6537,7 +6537,7 @@ function getWorktreeId(path) {
6537
6537
  function getWorktreePath(id) {
6538
6538
  return join(getWorktreesDir(), id);
6539
6539
  }
6540
- var CONFIG_FILENAME = "wsh.toml";
6540
+ var CONFIG_FILENAME = "workshell.toml";
6541
6541
  function getConfigPath() {
6542
6542
  const gitConfigPath = join(getGitCommonDir(), CONFIG_FILENAME);
6543
6543
  if (existsSync(gitConfigPath)) {
@@ -6594,7 +6594,7 @@ function getCommandName() {
6594
6594
  }
6595
6595
  const scriptName = basename2(scriptPath, ".js");
6596
6596
  if (scriptName === "index" || scriptName === "dist") {
6597
- return "wsh";
6597
+ return "wksh";
6598
6598
  }
6599
6599
  return scriptName;
6600
6600
  }
@@ -6760,22 +6760,11 @@ function canFastForward(baseBranch, targetBranch) {
6760
6760
  return false;
6761
6761
  }
6762
6762
  }
6763
- function inferBaseBranch(branch) {
6764
- const candidates = ["main", "master"];
6765
- for (const candidate of candidates) {
6766
- try {
6767
- execSync2(`git show-ref --verify --quiet refs/heads/${candidate}`);
6768
- return candidate;
6769
- } catch {
6770
- }
6771
- }
6772
- return "HEAD";
6773
- }
6774
6763
  function generateName() {
6775
6764
  const bytes = new Uint8Array(4);
6776
6765
  crypto.getRandomValues(bytes);
6777
6766
  const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
6778
- return `wsh-${hex}`;
6767
+ return `wksh-${hex}`;
6779
6768
  }
6780
6769
  function slugify(name) {
6781
6770
  return name.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -6802,9 +6791,9 @@ exit() {
6802
6791
  builtin exit "$@"
6803
6792
  fi
6804
6793
  # Support: exit -f / exit --force (strip before calling builtin exit)
6805
- local wsh_force=""
6794
+ local wksh_force=""
6806
6795
  if [[ "\${1:-}" == "-f" || "\${1:-}" == "--force" ]]; then
6807
- wsh_force="\${1:-}"
6796
+ wksh_force="\${1:-}"
6808
6797
  shift
6809
6798
  fi
6810
6799
  # Fail open if ${cmd2} not in PATH
@@ -6812,30 +6801,30 @@ exit() {
6812
6801
  builtin exit "$@"
6813
6802
  fi
6814
6803
  # Run preclose - exit code 1 means dirty, block exit
6815
- command ${cmd2} __preclose $wsh_force
6804
+ command ${cmd2} __preclose $wksh_force
6816
6805
  if [[ $? -eq 1 ]]; then
6817
6806
  return
6818
6807
  fi
6819
6808
  builtin exit "$@"
6820
6809
  }
6821
6810
 
6822
- __wsh_wrapper() {
6811
+ __wksh_wrapper() {
6823
6812
  case "$1" in
6824
6813
  close) shift; exit "$@" ;;
6825
6814
  *) command ${cmd2} "$@" ;;
6826
6815
  esac
6827
6816
  }
6828
- alias wsh='__wsh_wrapper'
6829
- ${cmd2.endsWith("-dev") ? `alias wsh-dev='__wsh_wrapper'` : ""}
6817
+ alias wksh='__wksh_wrapper'
6818
+ ${cmd2.endsWith("-dev") ? `alias wksh-dev='__wksh_wrapper'` : ""}
6830
6819
  `;
6831
6820
  const zshScript = `
6832
6821
  exit() {
6833
6822
  if [[ \${ZSH_SUBSHELL:-0} -gt 0 ]]; then
6834
6823
  builtin exit "$@"
6835
6824
  fi
6836
- local wsh_force=""
6825
+ local wksh_force=""
6837
6826
  if [[ "\${1:-}" == "-f" || "\${1:-}" == "--force" ]]; then
6838
- wsh_force="\${1:-}"
6827
+ wksh_force="\${1:-}"
6839
6828
  shift
6840
6829
  fi
6841
6830
  # Fail open if ${cmd2} not in PATH
@@ -6843,21 +6832,21 @@ exit() {
6843
6832
  builtin exit "$@"
6844
6833
  fi
6845
6834
  # Run preclose - exit code 1 means dirty, block exit
6846
- command ${cmd2} __preclose $wsh_force
6835
+ command ${cmd2} __preclose $wksh_force
6847
6836
  if [[ $? -eq 1 ]]; then
6848
6837
  return
6849
6838
  fi
6850
6839
  builtin exit "$@"
6851
6840
  }
6852
6841
 
6853
- __wsh_wrapper() {
6842
+ __wksh_wrapper() {
6854
6843
  case "$1" in
6855
6844
  close) shift; exit "$@" ;;
6856
6845
  *) command ${cmd2} "$@" ;;
6857
6846
  esac
6858
6847
  }
6859
- alias wsh='__wsh_wrapper'
6860
- ${cmd2.endsWith("-dev") ? `alias wsh-dev='__wsh_wrapper'` : ""}
6848
+ alias wksh='__wksh_wrapper'
6849
+ ${cmd2.endsWith("-dev") ? `alias wksh-dev='__wksh_wrapper'` : ""}
6861
6850
  `;
6862
6851
  const fishScript = `
6863
6852
  function exit --wraps=exit
@@ -6865,11 +6854,11 @@ function exit --wraps=exit
6865
6854
  if not status is-interactive
6866
6855
  builtin exit $argv
6867
6856
  end
6868
- set wsh_force
6857
+ set wksh_force
6869
6858
  set args $argv
6870
6859
  if test (count $args) -gt 0
6871
6860
  if test "$args[1]" = "-f"; or test "$args[1]" = "--force"
6872
- set wsh_force $args[1]
6861
+ set wksh_force $args[1]
6873
6862
  set -e args[1]
6874
6863
  end
6875
6864
  end
@@ -6878,14 +6867,14 @@ function exit --wraps=exit
6878
6867
  builtin exit $args
6879
6868
  end
6880
6869
  # Run preclose - exit code 1 means dirty, block exit
6881
- command ${cmd2} __preclose $wsh_force
6870
+ command ${cmd2} __preclose $wksh_force
6882
6871
  if test $status -eq 1
6883
6872
  return
6884
6873
  end
6885
6874
  builtin exit $args
6886
6875
  end
6887
6876
 
6888
- function __wsh_wrapper
6877
+ function __wksh_wrapper
6889
6878
  if test "$argv[1]" = "close"
6890
6879
  set args $argv
6891
6880
  set -e args[1]
@@ -6895,21 +6884,21 @@ function __wsh_wrapper
6895
6884
  end
6896
6885
  end
6897
6886
 
6898
- function wsh --wraps=${cmd2}
6899
- __wsh_wrapper $argv
6887
+ function wksh --wraps=${cmd2}
6888
+ __wksh_wrapper $argv
6900
6889
  end
6901
6890
  ${cmd2.endsWith("-dev") ? `
6902
- function wsh-dev --wraps=${cmd2}
6903
- __wsh_wrapper $argv
6891
+ function wksh-dev --wraps=${cmd2}
6892
+ __wksh_wrapper $argv
6904
6893
  end
6905
6894
  ` : ""}
6906
6895
  `;
6907
6896
  const setupSection = setupCommand ? `
6908
- # wsh setup script
6897
+ # wksh setup script
6909
6898
  ${setupCommand}
6910
6899
  ` : "";
6911
6900
  if (shell.endsWith("zsh")) {
6912
- const tmpDir = join2(tmpdir(), `wsh-${process.pid}`);
6901
+ const tmpDir = join2(tmpdir(), `wksh-${process.pid}`);
6913
6902
  mkdirSync2(tmpDir, { recursive: true });
6914
6903
  try {
6915
6904
  writeFileSync2(join2(tmpDir, ".zshrc"), `[[ -f "$HOME/.zshrc" ]] && source "$HOME/.zshrc"
@@ -6919,7 +6908,7 @@ ${zshScript}${setupSection}`);
6919
6908
  rmSync(tmpDir, { recursive: true, force: true });
6920
6909
  }
6921
6910
  } else if (shell.endsWith("bash")) {
6922
- const tmpDir = join2(tmpdir(), `wsh-${process.pid}`);
6911
+ const tmpDir = join2(tmpdir(), `wksh-${process.pid}`);
6923
6912
  mkdirSync2(tmpDir, { recursive: true });
6924
6913
  try {
6925
6914
  const rcFile = join2(tmpDir, ".bashrc");
@@ -6931,12 +6920,12 @@ ${bashScript}${setupSection}`);
6931
6920
  }
6932
6921
  } else if (shell.endsWith("fish")) {
6933
6922
  const fishInit = setupCommand ? `${fishScript}
6934
- # wsh setup script
6923
+ # wksh setup script
6935
6924
  ${setupCommand}` : fishScript;
6936
6925
  spawnSync(shell, ["--init-command", fishInit], { cwd, stdio: "inherit" });
6937
6926
  } else {
6938
6927
  console.error(`Error: Unsupported shell '${shell}'`);
6939
- console.error("wsh requires bash, zsh, or fish.");
6928
+ console.error("wksh requires bash, zsh, or fish.");
6940
6929
  process.exit(1);
6941
6930
  }
6942
6931
  }
@@ -6954,20 +6943,19 @@ function autoCleanupWorktree(worktreeId, path, store, saveStore2) {
6954
6943
  const status = getWorktreeStatus(path);
6955
6944
  if (status !== "clean") {
6956
6945
  console.log(" " + warn(`Branch '${branch}' has uncommitted changes. Worktree kept.`));
6957
- console.log(dim(` To reopen: `) + cyan(`wsh open ${branch}`));
6946
+ console.log(dim(` To reopen: `) + cyan(`wksh open ${branch}`));
6958
6947
  return;
6959
6948
  }
6960
- const meta = getWorktreeMeta(store, worktreeId);
6961
- const baseBranch = meta?.base_branch;
6949
+ const currentBranch = getCurrentBranch();
6950
+ console.log(" Pruning worktree...");
6962
6951
  try {
6963
6952
  removeGitWorktree(path);
6964
6953
  } catch {
6965
6954
  }
6966
- console.log(" Pruning worktree...");
6967
6955
  pruneWorktrees();
6968
6956
  removeWorktreeMeta(store, worktreeId);
6969
6957
  saveStore2(store);
6970
- const canMergeBranch = branch !== "[missing]" && branch !== "[detached]" && baseBranch;
6958
+ const canMergeBranch = branch !== "[missing]" && branch !== "[detached]" && currentBranch !== "HEAD";
6971
6959
  if (!canMergeBranch) {
6972
6960
  console.log(` Pruned '${branch}' worktree`);
6973
6961
  return;
@@ -6976,19 +6964,14 @@ function autoCleanupWorktree(worktreeId, path, store, saveStore2) {
6976
6964
  console.log(` Changes have been pushed to a remote.`);
6977
6965
  console.log(` Pruned worktree. Your changes are still in the '${cyan(branch)}' branch.`);
6978
6966
  console.log(` To check it out again later:`);
6979
- console.log(dim(` `) + cyan(`wsh open ${branch}`));
6967
+ console.log(dim(` `) + cyan(`wksh open ${branch}`));
6980
6968
  return;
6981
6969
  }
6982
6970
  console.log(` Pruned worktree. Your changes are still in the '${cyan(branch)}' branch.`);
6983
6971
  console.log(` To merge your changes:`);
6984
- const currentBranch = getCurrentBranch();
6985
- if (currentBranch === baseBranch) {
6986
- console.log(dim(` git merge ${cyan(branch)}`));
6987
- } else {
6988
- console.log(dim(` git checkout ${cyan(baseBranch)} && git merge ${cyan(branch)}`));
6989
- }
6972
+ console.log(dim(` git merge ${cyan(branch)}`));
6990
6973
  console.log();
6991
- if (!canFastForward(baseBranch, branch)) {
6974
+ if (!canFastForward(currentBranch, branch)) {
6992
6975
  return;
6993
6976
  }
6994
6977
  if (store.auto_merge_prompt === false) {
@@ -7005,7 +6988,7 @@ function autoCleanupWorktree(worktreeId, path, store, saveStore2) {
7005
6988
  execSync2(`git merge "${branch}"`, { stdio: "inherit" });
7006
6989
  deleteBranch(branch);
7007
6990
  console.log();
7008
- console.log(success(`Merged '${cyan(branch)}' into '${cyan(baseBranch)}'`));
6991
+ console.log(success(`Merged '${cyan(branch)}' into '${cyan(currentBranch)}'`));
7009
6992
  } catch {
7010
6993
  console.log();
7011
6994
  console.log(warn(`Merge failed. Resolve conflicts manually, then delete branch with:`));
@@ -7031,12 +7014,12 @@ function newCommand(branchName, fromBranch) {
7031
7014
  const existingWorktree = getWorktreeForBranch(branch);
7032
7015
  if (existingWorktree) {
7033
7016
  console.error(`Error: branch '${branch}' already has an active worktree`);
7034
- console.error(dim(` Try: `) + cyan(`wsh open ${branch}`));
7017
+ console.error(dim(` Try: `) + cyan(`wksh open ${branch}`));
7035
7018
  process.exit(1);
7036
7019
  }
7037
7020
  if (branchExists(branch)) {
7038
7021
  console.error(`Error: branch '${branch}' already exists`);
7039
- console.error(dim(` Try: `) + cyan(`wsh open ${branch}`));
7022
+ console.error(dim(` Try: `) + cyan(`wksh open ${branch}`));
7040
7023
  process.exit(1);
7041
7024
  }
7042
7025
  const store = loadStore();
@@ -7047,7 +7030,6 @@ function newCommand(branchName, fromBranch) {
7047
7030
  mkdirSync3(dirname3(worktreePath), { recursive: true });
7048
7031
  createWorktree(branch, worktreePath, fromBranch);
7049
7032
  setWorktreeMeta(store, worktreeId, {
7050
- base_branch: baseBranch,
7051
7033
  created_at: (/* @__PURE__ */ new Date()).toISOString()
7052
7034
  });
7053
7035
  saveStore(store);
@@ -7055,7 +7037,7 @@ function newCommand(branchName, fromBranch) {
7055
7037
  console.log();
7056
7038
  console.log(success(bold(branch)), dim(`(from ${cyan(baseBranch)})`));
7057
7039
  console.log(dim(`Opened branch in ephemeral subshell at ${relative(mainWorktree, worktreePath)}`));
7058
- console.log(dim("Type 'wsh close' to return."));
7040
+ console.log(dim("Type 'wksh close' to return."));
7059
7041
  console.log();
7060
7042
  spawnShell(worktreePath, {
7061
7043
  branch,
@@ -7095,7 +7077,7 @@ function openCommand(branch) {
7095
7077
  const mainWorktree2 = getMainWorktree();
7096
7078
  console.log();
7097
7079
  console.log(success(bold(branch)), dim(`(existing worktree)`));
7098
- console.log(dim("Type 'wsh close' to return."));
7080
+ console.log(dim("Type 'wksh close' to return."));
7099
7081
  console.log();
7100
7082
  spawnShell(existingWorktreePath, {
7101
7083
  branch,
@@ -7116,16 +7098,14 @@ function openCommand(branch) {
7116
7098
  const worktreePath = join4(getWorktreesDir(), worktreeId);
7117
7099
  mkdirSync4(dirname4(worktreePath), { recursive: true });
7118
7100
  createWorktreeForExistingBranch(branch, worktreePath);
7119
- const baseBranch = inferBaseBranch(branch);
7120
7101
  setWorktreeMeta(store, worktreeId, {
7121
- base_branch: baseBranch,
7122
7102
  created_at: (/* @__PURE__ */ new Date()).toISOString()
7123
7103
  });
7124
7104
  saveStore(store);
7125
7105
  console.log();
7126
7106
  console.log(success(bold(branch)));
7127
7107
  console.log(dim(`Opened branch in ephemeral subshell at ${relative2(mainWorktree, worktreePath)}`));
7128
- console.log(dim("Type 'exit' or 'wsh close' to return."));
7108
+ console.log(dim("Type 'exit' or 'wksh close' to return."));
7129
7109
  console.log();
7130
7110
  spawnShell(worktreePath, {
7131
7111
  branch,
@@ -8007,14 +7987,14 @@ function rmCommand(branch, force = false) {
8007
7987
  const absCwd = resolve2(cwd);
8008
7988
  if (absCwd === absWtPath || absCwd.startsWith(absWtPath + "/")) {
8009
7989
  console.error("Error: cannot remove current worktree");
8010
- console.error(dim(` Try: `) + cyan(`wsh close`));
7990
+ console.error(dim(` Try: `) + cyan(`wksh close`));
8011
7991
  process.exit(1);
8012
7992
  }
8013
7993
  const status = getWorktreeStatus(worktreePath);
8014
7994
  const isDirty = status !== "clean" && status !== "";
8015
7995
  if (isDirty && !force) {
8016
7996
  console.error(warn("Uncommitted changes found. Commit, stash, or reset your changes first."));
8017
- console.error(dim(` Or run: `) + cyan(`wsh rm ${branch} -f`));
7997
+ console.error(dim(` Or run: `) + cyan(`wksh rm ${branch} -f`));
8018
7998
  process.exit(1);
8019
7999
  }
8020
8000
  if (isDirty && force) {
@@ -8127,7 +8107,7 @@ function precloseCommand(force) {
8127
8107
  }
8128
8108
  console.error(warn("Uncommitted changes found. Commit, stash, or reset your changes first."));
8129
8109
  console.error(dim(" Or use --force/-f to discard changes"));
8130
- console.error(" " + cyan("wsh close -f"));
8110
+ console.error(" " + cyan("wksh close -f"));
8131
8111
  process.exit(1);
8132
8112
  }
8133
8113
 
@@ -8135,7 +8115,7 @@ function precloseCommand(force) {
8135
8115
  var import_picocolors3 = __toESM(require_picocolors(), 1);
8136
8116
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, openSync as openSync2, readSync as readSync2 } from "fs";
8137
8117
  import { join as join5 } from "path";
8138
- var CONFIG_TEMPLATE = `# wsh configuration
8118
+ var CONFIG_TEMPLATE = `# workshell configuration
8139
8119
  # https://github.com/colinhacks/workshell
8140
8120
 
8141
8121
  # Initialization script to run when opening a worktree.
@@ -8165,15 +8145,15 @@ function configCommand() {
8165
8145
  console.log("No config file found.");
8166
8146
  console.log();
8167
8147
  console.log("Where would you like to create one?");
8168
- console.log(` ${import_picocolors3.default.bold("1.")} ${cyan(".git/wsh.toml")} ${dim("(local only, not committed)")}`);
8169
- console.log(` ${import_picocolors3.default.bold("2.")} ${cyan("wsh.toml")} ${dim("(project root, can be committed)")}`);
8148
+ console.log(` ${import_picocolors3.default.bold("1.")} ${cyan(".git/workshell.toml")} ${dim("(local only, not committed)")}`);
8149
+ console.log(` ${import_picocolors3.default.bold("2.")} ${cyan("workshell.toml")} ${dim("(project root, can be committed)")}`);
8170
8150
  console.log();
8171
8151
  const choice = promptChoice();
8172
8152
  if (!choice) {
8173
8153
  console.log(dim("Cancelled."));
8174
8154
  return;
8175
8155
  }
8176
- const targetPath = choice === 1 ? join5(getGitCommonDir(), "wsh.toml") : join5(getMainWorktree(), "wsh.toml");
8156
+ const targetPath = choice === 1 ? join5(getGitCommonDir(), "workshell.toml") : join5(getMainWorktree(), "workshell.toml");
8177
8157
  writeFileSync3(targetPath, CONFIG_TEMPLATE);
8178
8158
  console.log();
8179
8159
  console.log(success(`Created ${cyan(targetPath)}`));
@@ -8193,14 +8173,14 @@ function promptChoice() {
8193
8173
  }
8194
8174
 
8195
8175
  // index.ts
8196
- var VERSION = "0.0.2";
8176
+ var VERSION = "0.0.3";
8197
8177
  function printHelp() {
8198
8178
  const dim2 = import_picocolors4.default.dim;
8199
8179
  const cyan2 = import_picocolors4.default.cyan;
8200
8180
  const green2 = import_picocolors4.default.green;
8201
- console.log(`${import_picocolors4.default.bold("wsh")} ${dim2(`v${VERSION}`)} - Open branches in ephemeral subshells
8181
+ console.log(`${import_picocolors4.default.bold("wksh")} ${dim2(`v${VERSION}`)} - Open branches in ephemeral subshells
8202
8182
 
8203
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh")} ${dim2("<command>")} ${dim2("[options]")}
8183
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh")} ${dim2("<command>")} ${dim2("[options]")}
8204
8184
 
8205
8185
  ${import_picocolors4.default.bold("Commands:")}
8206
8186
  ${green2("new")} ${dim2("[branch]")} Create a branch and open it ${dim2("[--from <branch>]")}
@@ -8217,15 +8197,15 @@ ${import_picocolors4.default.bold("Options:")}
8217
8197
 
8218
8198
  ${dim2("\u2500".repeat(60))}
8219
8199
 
8220
- ${import_picocolors4.default.bold("Why wsh?")}
8200
+ ${import_picocolors4.default.bold("Why wksh?")}
8221
8201
 
8222
8202
  In agentic development, you often want to spin up isolated workspaces for
8223
- specific tasks\u2014without stashing or committing your current changes. wsh makes
8203
+ specific tasks\u2014without stashing or committing your current changes. wksh makes
8224
8204
  this effortless.
8225
8205
 
8226
8206
  Open a new branch in an ephemeral subshell:
8227
8207
 
8228
- ${dim2("$")} ${cyan2("wsh new add-login-button")}
8208
+ ${dim2("$")} ${cyan2("wksh new add-login-button")}
8229
8209
 
8230
8210
  This creates a branch and worktree, then drops you into a subshell. Open it
8231
8211
  in your editor or point an AI coding agent at it. Your changes are completely
@@ -8239,24 +8219,24 @@ Or merge directly back into main:
8239
8219
 
8240
8220
  ${dim2("$")} ${cyan2("git checkout main && git merge add-login-button")}
8241
8221
 
8242
- Then just ${cyan2("wsh close")} to return to your main repo. The worktree is ephemeral\u2014
8222
+ Then just ${cyan2("wksh close")} to return to your main repo. The worktree is ephemeral\u2014
8243
8223
  it gets cleaned up automatically.
8244
8224
 
8245
- Use ${cyan2("wsh ls")} to see branches with open worktrees, and ${cyan2("wsh open <branch>")} to
8225
+ Use ${cyan2("wksh ls")} to see branches with open worktrees, and ${cyan2("wksh open <branch>")} to
8246
8226
  reopen one later.`);
8247
8227
  }
8248
8228
  function printVersion() {
8249
- console.log(`wsh v${VERSION}`);
8229
+ console.log(`wksh v${VERSION}`);
8250
8230
  }
8251
8231
  function printNewHelp() {
8252
8232
  const dim2 = import_picocolors4.default.dim;
8253
8233
  const cyan2 = import_picocolors4.default.cyan;
8254
- console.log(`${import_picocolors4.default.bold("wsh new")} - Create a branch and open it in an ephemeral subshell
8234
+ console.log(`${import_picocolors4.default.bold("wksh new")} - Create a branch and open it in an ephemeral subshell
8255
8235
 
8256
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh new")} ${dim2("[branch]")} ${dim2("[--from <branch>]")}
8236
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh new")} ${dim2("[branch]")} ${dim2("[--from <branch>]")}
8257
8237
 
8258
8238
  ${import_picocolors4.default.bold("Arguments:")}
8259
- ${dim2("[branch]")} Branch name ${dim2('(default: "wsh-[hash]")')}
8239
+ ${dim2("[branch]")} Branch name ${dim2('(default: "wksh-[hash]")')}
8260
8240
 
8261
8241
  ${import_picocolors4.default.bold("Options:")}
8262
8242
  ${cyan2("--from")} ${dim2("<branch>")} Base branch to fork from ${dim2("(default: current branch)")}
@@ -8264,14 +8244,14 @@ ${import_picocolors4.default.bold("Options:")}
8264
8244
  ${import_picocolors4.default.bold("Description:")}
8265
8245
  Creates a new Git branch (forked from current or specified branch) and opens
8266
8246
  it in an ephemeral subshell with its own worktree.
8267
- Type 'wsh close' to return.`);
8247
+ Type 'wksh close' to return.`);
8268
8248
  }
8269
8249
  function printOpenHelp() {
8270
8250
  const dim2 = import_picocolors4.default.dim;
8271
8251
  const cyan2 = import_picocolors4.default.cyan;
8272
- console.log(`${import_picocolors4.default.bold("wsh open")} - Open a branch in an ephemeral subshell
8252
+ console.log(`${import_picocolors4.default.bold("wksh open")} - Open a branch in an ephemeral subshell
8273
8253
 
8274
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh open")} ${dim2("<branch>")}
8254
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh open")} ${dim2("<branch>")}
8275
8255
 
8276
8256
  ${import_picocolors4.default.bold("Arguments:")}
8277
8257
  ${dim2("<branch>")} Branch name ${dim2("(required)")}
@@ -8280,14 +8260,14 @@ ${import_picocolors4.default.bold("Description:")}
8280
8260
  Opens the specified branch in an ephemeral subshell.
8281
8261
  If a worktree already exists for the branch, uses that.
8282
8262
  Otherwise, creates a new worktree automatically.
8283
- Type 'wsh close' to return.`);
8263
+ Type 'wksh close' to return.`);
8284
8264
  }
8285
8265
  function printLsHelp() {
8286
8266
  const dim2 = import_picocolors4.default.dim;
8287
8267
  const cyan2 = import_picocolors4.default.cyan;
8288
- console.log(`${import_picocolors4.default.bold("wsh ls")} - List branches with open worktrees
8268
+ console.log(`${import_picocolors4.default.bold("wksh ls")} - List branches with open worktrees
8289
8269
 
8290
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh ls")} ${dim2("[options]")}
8270
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh ls")} ${dim2("[options]")}
8291
8271
 
8292
8272
  ${import_picocolors4.default.bold("Options:")}
8293
8273
  ${cyan2("--plain")} Output in plain tab-separated format
@@ -8300,9 +8280,9 @@ ${import_picocolors4.default.bold("Description:")}
8300
8280
  function printRmHelp() {
8301
8281
  const dim2 = import_picocolors4.default.dim;
8302
8282
  const cyan2 = import_picocolors4.default.cyan;
8303
- console.log(`${import_picocolors4.default.bold("wsh rm")} - Remove a branch's worktree
8283
+ console.log(`${import_picocolors4.default.bold("wksh rm")} - Remove a branch's worktree
8304
8284
 
8305
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh rm")} ${dim2("<branch>")} ${dim2("[options]")}
8285
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh rm")} ${dim2("<branch>")} ${dim2("[options]")}
8306
8286
 
8307
8287
  ${import_picocolors4.default.bold("Arguments:")}
8308
8288
  ${dim2("<branch>")} Branch whose worktree to remove ${dim2("(required)")}
@@ -8317,9 +8297,9 @@ ${import_picocolors4.default.bold("Description:")}
8317
8297
  }
8318
8298
  function printStatusHelp() {
8319
8299
  const cyan2 = import_picocolors4.default.cyan;
8320
- console.log(`${import_picocolors4.default.bold("wsh status")} - Show current branch status
8300
+ console.log(`${import_picocolors4.default.bold("wksh status")} - Show current branch status
8321
8301
 
8322
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh status")}
8302
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh status")}
8323
8303
 
8324
8304
  ${import_picocolors4.default.bold("Description:")}
8325
8305
  Shows the current branch, worktree path, and git status.`);
@@ -8327,9 +8307,9 @@ ${import_picocolors4.default.bold("Description:")}
8327
8307
  function printCloseHelp() {
8328
8308
  const dim2 = import_picocolors4.default.dim;
8329
8309
  const cyan2 = import_picocolors4.default.cyan;
8330
- console.log(`${import_picocolors4.default.bold("wsh close")} - Exit current subshell
8310
+ console.log(`${import_picocolors4.default.bold("wksh close")} - Exit current subshell
8331
8311
 
8332
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh close")} ${dim2("[options]")}
8312
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh close")} ${dim2("[options]")}
8333
8313
 
8334
8314
  ${import_picocolors4.default.bold("Options:")}
8335
8315
  ${cyan2("-f")}, ${cyan2("--force")} Discard uncommitted changes and exit
@@ -8348,17 +8328,17 @@ ${import_picocolors4.default.bold("Description:")}
8348
8328
  function printConfigHelp() {
8349
8329
  const dim2 = import_picocolors4.default.dim;
8350
8330
  const cyan2 = import_picocolors4.default.cyan;
8351
- console.log(`${import_picocolors4.default.bold("wsh config")} - Show or create config file
8331
+ console.log(`${import_picocolors4.default.bold("wksh config")} - Show or create config file
8352
8332
 
8353
- ${import_picocolors4.default.bold("Usage:")} ${cyan2("wsh config")}
8333
+ ${import_picocolors4.default.bold("Usage:")} ${cyan2("wksh config")}
8354
8334
 
8355
8335
  ${import_picocolors4.default.bold("Description:")}
8356
- Shows the path and contents of your wsh config file.
8336
+ Shows the path and contents of your workshell config file.
8357
8337
  If no config exists, prompts to create one.
8358
8338
 
8359
8339
  ${import_picocolors4.default.bold("Config locations")} ${dim2("(in precedence order)")}${import_picocolors4.default.bold(":")}
8360
- ${cyan2(".git/wsh.toml")} Local only, not committed
8361
- ${cyan2("wsh.toml")} Project root, can be committed`);
8340
+ ${cyan2(".git/workshell.toml")} Local only, not committed
8341
+ ${cyan2("workshell.toml")} Project root, can be committed`);
8362
8342
  }
8363
8343
  function isHelp(arg) {
8364
8344
  return arg === "--help" || arg === "-h";
@@ -8401,7 +8381,7 @@ switch (cmd) {
8401
8381
  process.exit(0);
8402
8382
  }
8403
8383
  if (!args[1]) {
8404
- console.error("Usage: wsh open <branch>");
8384
+ console.error("Usage: wksh open <branch>");
8405
8385
  process.exit(1);
8406
8386
  }
8407
8387
  openCommand(args[1]);
@@ -8426,7 +8406,7 @@ switch (cmd) {
8426
8406
  process.exit(0);
8427
8407
  }
8428
8408
  if (!args[1]) {
8429
- console.error("Usage: wsh rm <branch>");
8409
+ console.error("Usage: wksh rm <branch>");
8430
8410
  process.exit(1);
8431
8411
  }
8432
8412
  rmCommand(args[1], args.includes("-f") || args.includes("--force"));
@@ -8443,7 +8423,7 @@ switch (cmd) {
8443
8423
  printCloseHelp();
8444
8424
  process.exit(0);
8445
8425
  }
8446
- console.error(fail("'wsh close' only works inside a wsh subshell."));
8426
+ console.error(fail("'wksh close' only works inside a wksh subshell."));
8447
8427
  process.exit(1);
8448
8428
  break;
8449
8429
  case "__preclose":
@@ -8451,7 +8431,7 @@ switch (cmd) {
8451
8431
  break;
8452
8432
  default:
8453
8433
  console.error(`Unknown command: ${cmd}`);
8454
- console.error("Run 'wsh --help' for usage.");
8434
+ console.error("Run 'wksh --help' for usage.");
8455
8435
  process.exit(1);
8456
8436
  }
8457
8437
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workshell",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Agent- and human-friendly Git multitasking, powered by worktrees",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "isolation"
23
23
  ],
24
24
  "bin": {
25
- "wsh": "dist/index.js",
25
+ "wksh": "dist/index.js",
26
26
  "workshell": "dist/index.js",
27
27
  "git-workshell": "dist/index.js"
28
28
  },
@@ -32,7 +32,7 @@
32
32
  "sideEffects": false,
33
33
  "scripts": {
34
34
  "build": "tsc && node build.ts",
35
- "postbuild": "ln -sf $(pwd)/dist/index.js /usr/local/bin/wsh-dev",
35
+ "postbuild": "ln -sf $(pwd)/dist/index.js /usr/local/bin/wksh-dev",
36
36
  "dev": "node build.ts --watch",
37
37
  "prepare": "husky && node build.ts",
38
38
  "typecheck": "tsc",