Grove ships a grove command-line tool alongside the desktop app — the same
binary serves both. Run it with no arguments to launch the GUI; run it with a
subcommand to stay in the terminal.
The CLI follows a <noun> <verb> pattern (grove worktree new, grove hook run, grove config set) and reuses the exact logic of the GUI: it reads the
same ~/.grove/store.json, runs the same hooks, and shells out to the same
git.
In the app, go to Settings → CLI Command → Install CLI. This symlinks the
running binary to /usr/local/bin/grove:
/usr/local/bin/grove -> /Applications/Grove.app/Contents/MacOS/Grove
If /usr/local/bin isn't writable, the app prints the manual command to run:
sudo ln -sf /Applications/Grove.app/Contents/MacOS/Grove /usr/local/bin/groveVerify with grove --version.
| Command | Description |
|---|---|
grove [path] |
Open a repository in the Grove GUI (defaults to .) |
grove open [path] |
Same as above, explicit form |
grove worktree list |
List worktrees for the current repo |
grove worktree new <branch> |
Create a worktree for a branch |
grove worktree detach [path] |
Move the current branch into its own worktree |
grove worktree attach [branch] |
Fold a worktree's branch back onto the main worktree |
grove worktree rm [branch] |
Remove a worktree |
grove hook run <event> |
Run hooks for a lifecycle event |
grove hook list |
List configured hooks |
grove hook edit |
Edit hooks in $EDITOR |
grove config show |
Print the effective configuration |
grove config get/set/unset <key> |
Read or write a config value |
grove config path |
Print the path to store.json |
grove config edit |
Edit the full repo config in $EDITOR |
grove cd [branch] |
Print a worktree path (pair with shell-init to actually cd) |
grove shell-init <shell> |
Print a shell snippet that wires grove cd into your shell |
The worktree lifecycle verbs also have shorter top-level aliases: grove new,
grove detach, grove attach, grove rm.
grove . # open the current repo in the GUI
grove open ~/code/myapp # open a specific repogrove open forwards to the already-running instance, so repeated calls reuse
the same window rather than spawning new ones.
Lists every worktree for the current repository. The main worktree is marked
with *, and dirty worktrees are flagged.
* /Users/me/myapp main
/Users/me/myapp/.worktrees/login feat/login [dirty]
Creates a worktree, running pre-create / post-create hooks.
grove worktree new feat/login # new branch off the default base
grove worktree new fix/bug -b release/2.0 # new branch off a specific base
grove worktree new feat/login --existing # use an existing local branch
grove worktree new feat/login -r origin/feat/login # track a remote branch
grove worktree new feat/login -p ../login-wt # custom worktree path| Flag | Description |
|---|---|
-b, --base <REF> |
Base ref for new branches (default: configured base branch) |
-p, --path <PATH> |
Custom worktree path (overrides the configured worktree root) |
-r, --remote <REMOTE_REF> |
Track a remote branch and set upstream |
--existing |
Use an existing local branch instead of creating one |
--no-hooks |
Skip pre-create / post-create hooks |
-q, --quiet |
Print only the resulting path on stdout (logs go to stderr) |
--quiet makes the command scriptable:
cd "$(grove worktree new feat/login -q)"Moves the main worktree's current branch into a new linked worktree, then
switches the main worktree back onto the base branch. Useful when you started
work on main and want to move it aside. Requires a clean main worktree.
grove worktree detach # detach the current branch
grove worktree detach ../my-feature # detach to a specific path
grove worktree detach -b develop # switch main back onto a specific baseAlso available as
grove detach. The legacy aliasgrove convertstill works.
The inverse of detach: removes a linked worktree and switches the main
worktree onto its branch. Uncommitted changes are carried over via a stash, so
committed work is never at risk.
grove worktree attach feat/login # attach by branch name
grove worktree attach # attach the worktree you're standing in
grove worktree attach -y # skip the confirmation prompt| Flag | Description |
|---|---|
-y, --yes |
Skip the confirmation prompt |
-f, --force |
Force removal of a locked worktree |
--no-hooks |
Skip pre-remove / post-remove hooks |
Removes a worktree. With no branch argument, removes the worktree containing the current directory (never the main worktree).
grove worktree rm feat/login # remove by branch name
grove worktree rm # remove the worktree you're standing in
grove worktree rm feat/login -y # skip confirmation
grove worktree rm feat/login --dry-run # preview without removing
grove worktree rm feat/login -f --prune # force, then prune stale entries| Flag | Description |
|---|---|
-y, --yes |
Skip the confirmation prompt |
-f, --force |
Force removal (allows dirty worktrees, unlocks locked ones) |
--dry-run |
Print what would happen without making changes |
--no-hooks |
Skip pre-remove / post-remove hooks |
--prune |
Run git worktree prune after removal |
Also available as
grove rm/grove remove. When not attached to a TTY, the confirmation can't be shown, so scripts must pass-y.
See hooks.md for the full hook model. From the CLI:
Runs the configured steps for a lifecycle event. The repo root and worktree are auto-detected from the current directory.
grove hook run post-create # run post-create in the current worktree
grove hook run post-create --worktree ../login-wt # target a specific worktreeEvents: pre-create, post-create, pre-launch, post-launch,
pre-remove, post-remove.
Prints the configured hooks for the current repository, grouped by event.
Opens the repo's hooks in $EDITOR (TOML). Invalid TOML re-prompts so you can
fix it without losing your edits.
See configuration.md for how config layers merge. From the CLI:
Prints the effective (merged) configuration for the current repo, annotating where each value comes from.
grove config show # effective merged view
grove config show --global # only the app-wide global layer
grove config show --repo # only this repo's overridesgrove config get worktree_root # effective value
grove config set worktree_root .worktrees # per-repo override
grove config set worktree_root .wt --global # app-wide default
grove config unset worktree_root # clear the per-repo valueSupported keys: worktree_root, default_base_branch. The config
subcommand accepts snake_case or camelCase (worktree_root / worktreeRoot) —
note this differs from the kebab-case keys used in the TOML config file
(worktree-root). Per-repo is the default scope; --global writes the app-wide
default (only worktree_root supports a global layer).
grove config path # print ~/.grove/store.json
grove config edit # edit the full repo config (settings + launchers + hooks)grove cd prints a worktree's path; on its own it can't change your shell's
directory. Wire it up with shell-init once and grove cd <branch> will cd
for you.
Add to your shell rc file:
# ~/.zshrc or ~/.bashrc
eval "$(grove shell-init zsh)" # or: bash
# ~/.config/fish/config.fish
grove shell-init fish | sourceThen:
grove cd feat/login # cd into the feat/login worktree
grove cd login # substring match also works
grove cd # cd back to the main worktreeThe shell wrapper also makes grove detach and grove attach change your
directory to the resulting worktree after they run.