You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today wt puts everything under one custom directory, ~/.wt/:
File
Purpose
~/.wt/wt
binary
~/.wt/config.toml
global config
~/.wt/sessions.json
windows-mode session state
This is non-standard. On Linux (and increasingly on macOS), the expected layout is XDG Base Directory plus the systemd file hierarchy:
Concern
Standard location (Linux default)
User binary
$HOME/.local/bin/
User config
$XDG_CONFIG_HOME (default $HOME/.config/)
User state
$XDG_STATE_HOME (default $HOME/.local/state/)
Because ~/.wt/ is not on PATH, install.sh currently has to add a shell-rc alias in ~/.bashrc / ~/.zshrc / ~/.config/fish/config.fish just to make wt callable. Moving the binary onto ~/.local/bin/ (which is on PATH by default on every modern Linux distro and easily added on macOS) removes the alias dance entirely.
What to build
Move wt's on-disk footprint off the custom ~/.wt/ directory and onto standard locations: ~/.local/bin/ for the binary, $XDG_CONFIG_HOME/wt/ for config, $XDG_STATE_HOME/wt/ for state.
Target layout
File
Old
New (Linux & macOS default)
Binary
~/.wt/wt (with shell alias)
~/.local/bin/wt (on PATH)
Global config
~/.wt/config.toml
~/.config/wt/config.toml
Session state
~/.wt/sessions.json
~/.local/state/wt/sessions.json
XDG_CONFIG_HOME / XDG_STATE_HOME env vars are honoured. Per-repo .wt.toml is unchanged.
macOS gets the same XDG paths, not ~/Library/Application Support/. This matches the convention used by gh, starship, zoxide, and most modern CLI tools, and gives macOS users with mixed Linux/macOS dotfiles a single ~/.config/ tree.
Design
Binary default: ~/.local/bin/wt on both platforms.
--system flag on install.sh: installs to /usr/local/bin/wt (with sudo where required). Useful for shared machines.
Path resolution in code: hand-rolled XDG logic (read XDG_CONFIG_HOME / XDG_STATE_HOME, fall back to dirs::home_dir().join(".config") / home_dir().join(".local/state")). Do not use dirs::config_dir() / dirs::state_dir() — they return Apple paths on macOS, which is the opposite of the chosen design.
Migration: read-with-fallback in code plus an explicit migration step in install.sh. Existing users keep working without intervention; running the new installer moves their files cleanly.
PATH on macOS: ~/.local/bin/ is not on $PATH by default on macOS. install.sh detects this and prints a one-line instruction for the user to add it themselves — it does not write to rc files (eliminating the alias dance is the point of this issue).
Implementation notes
install.sh
Replace the hard-coded INSTALL_DIR="$HOME/.wt" with logic that picks ~/.local/bin/ by default and mkdir -ps it.
Add --system. When passed, install to /usr/local/bin/wt using sudo if not already root.
After the copy, check whether the chosen install dir is on the user's PATH. If not, print a one-line instruction; do not write a shell-rc alias.
Drop the alias-writing block entirely when the binary is on PATH.
Migration order (so the user's wt command is never broken mid-flight):
Copy the new binary to the chosen install dir.
Remove the legacy alias from shell rc files (~/.bashrc, ~/.bash_profile, ~/.zshrc, ~/.config/fish/config.fish). This is required, not optional: shell aliases take precedence over PATH, so a stale alias wt='$HOME/.wt/wt' would shadow the new binary and resolve to a path we are about to delete, breaking the wt command. Remove only the exact lines install.sh would have added (the alias line and the # wt - Git worktree orchestrator comment that precedes it), guarded by grep -q to stay idempotent.
Delete ~/.wt/wt.
If ~/.wt/config.toml exists and the new $XDG_CONFIG_HOME/wt/config.toml does not, move it (creating the parent dir as needed).
Same for ~/.wt/sessions.json → $XDG_STATE_HOME/wt/sessions.json.
If ~/.wt/ is now empty, remove the directory.
Print one line per step so the user can audit what changed.
src/config.rs
Add a small xdg_config_home() helper: std::env::var_os("XDG_CONFIG_HOME").filter(|p| !p.is_empty()).map(PathBuf::from).or_else(|| dirs::home_dir().map(|h| h.join(".config"))). Same shape for xdg_state_home() (.local/state).
Change Config::load() and Config::load_for_repo() to resolve the global path as xdg_config_home().join("wt/config.toml").
Add a fallback layer: order is $XDG_CONFIG_HOME/wt/config.toml → legacy ~/.wt/config.toml → defaults. The existing load_layered infrastructure already supports multiple layers. If only the legacy location exists, log a one-line stderr notice the first time per process: wt: notice: reading config from legacy ~/.wt/config.toml; rerun ./install.sh to migrate.
Replace Config::ensure_wt_dir with Config::ensure_config_dir() and Config::ensure_state_dir(). Update call sites.
src/session.rs
Resolve the path as xdg_state_home().join("wt/sessions.json").
Read-with-fallback to legacy ~/.wt/sessions.json, with the same one-line notice on first read.
Writes always go to the new location.
README.md
Update "Configuration" to reference ~/.config/wt/config.toml (note legacy is still readable).
Update "Installation" to drop the alias mention; the binary lands on PATH.
Document ./install.sh --system and the migration behaviour.
Acceptance criteria
Fresh install (no ~/.wt/ present) puts the binary at ~/.local/bin/wt and writes nothing under ~/.wt/.
./install.sh --system installs to /usr/local/bin/wt (with sudo if necessary).
Newly-created config and state files land at $XDG_CONFIG_HOME/wt/config.toml and $XDG_STATE_HOME/wt/sessions.json respectively (defaults ~/.config/wt/config.toml and ~/.local/state/wt/sessions.json).
Existing user with ~/.wt/{wt,config.toml,sessions.json} runs the new install.sh and ends up with: files in the new locations, an empty (or removed) ~/.wt/ directory, and the legacy alias wt=... line removed from every shell rc that contained it. which wt in a fresh shell resolves to the new binary path, not an alias.
Re-running install.sh after a successful migration is idempotent — no duplicate edits, no errors, exit 0.
If a user with only legacy files runs wt directly (without re-running install.sh), the binary still finds and reads their legacy config and state, with a one-time stderr deprecation notice.
XDG_CONFIG_HOME and XDG_STATE_HOME env vars are honoured (verified by test).
install.sh does not write any shell-rc alias under any code path. If the install dir is not on PATH, it prints a single instruction line.
macOS install lands the binary at ~/.local/bin/wt, config at ~/.config/wt/config.toml, state at ~/.local/state/wt/sessions.json — i.e. same XDG paths as Linux, not ~/Library/Application Support/. Documented in README.
On macOS, when ~/.local/bin/ is not on $PATH, install.sh prints a single instruction line and does not modify .zshrc / .bash_profile / config.fish.
Integration test exercises read-with-fallback by setting HOME to a temp dir and seeding only the legacy paths.
README "Configuration" and "Installation" sections updated.
Out of scope
A wt --uninstall flow.
Packaging for distro repos (deb, rpm, AUR, Homebrew tap). Once paths are standard, packaging becomes feasible — file as a follow-up.
Removing legacy-path support in code. Keep it indefinitely until a future minor version explicitly drops it.
Background
Today
wtputs everything under one custom directory,~/.wt/:~/.wt/wt~/.wt/config.toml~/.wt/sessions.jsonThis is non-standard. On Linux (and increasingly on macOS), the expected layout is XDG Base Directory plus the systemd file hierarchy:
$HOME/.local/bin/$XDG_CONFIG_HOME(default$HOME/.config/)$XDG_STATE_HOME(default$HOME/.local/state/)Because
~/.wt/is not onPATH,install.shcurrently has to add a shell-rc alias in~/.bashrc/~/.zshrc/~/.config/fish/config.fishjust to makewtcallable. Moving the binary onto~/.local/bin/(which is onPATHby default on every modern Linux distro and easily added on macOS) removes the alias dance entirely.What to build
Move
wt's on-disk footprint off the custom~/.wt/directory and onto standard locations:~/.local/bin/for the binary,$XDG_CONFIG_HOME/wt/for config,$XDG_STATE_HOME/wt/for state.Target layout
~/.wt/wt(with shell alias)~/.local/bin/wt(onPATH)~/.wt/config.toml~/.config/wt/config.toml~/.wt/sessions.json~/.local/state/wt/sessions.jsonXDG_CONFIG_HOME/XDG_STATE_HOMEenv vars are honoured. Per-repo.wt.tomlis unchanged.macOS gets the same XDG paths, not
~/Library/Application Support/. This matches the convention used bygh,starship,zoxide, and most modern CLI tools, and gives macOS users with mixed Linux/macOS dotfiles a single~/.config/tree.Design
~/.local/bin/wton both platforms.--systemflag oninstall.sh: installs to/usr/local/bin/wt(withsudowhere required). Useful for shared machines.XDG_CONFIG_HOME/XDG_STATE_HOME, fall back todirs::home_dir().join(".config")/home_dir().join(".local/state")). Do not usedirs::config_dir()/dirs::state_dir()— they return Apple paths on macOS, which is the opposite of the chosen design.install.sh. Existing users keep working without intervention; running the new installer moves their files cleanly.~/.local/bin/is not on$PATHby default on macOS.install.shdetects this and prints a one-line instruction for the user to add it themselves — it does not write to rc files (eliminating the alias dance is the point of this issue).Implementation notes
install.shINSTALL_DIR="$HOME/.wt"with logic that picks~/.local/bin/by default andmkdir -ps it.--system. When passed, install to/usr/local/bin/wtusingsudoif not already root.PATH. If not, print a one-line instruction; do not write a shell-rc alias.PATH.wtcommand is never broken mid-flight):~/.bashrc,~/.bash_profile,~/.zshrc,~/.config/fish/config.fish). This is required, not optional: shell aliases take precedence overPATH, so a stalealias wt='$HOME/.wt/wt'would shadow the new binary and resolve to a path we are about to delete, breaking thewtcommand. Remove only the exact linesinstall.shwould have added (the alias line and the# wt - Git worktree orchestratorcomment that precedes it), guarded bygrep -qto stay idempotent.~/.wt/wt.~/.wt/config.tomlexists and the new$XDG_CONFIG_HOME/wt/config.tomldoes not, move it (creating the parent dir as needed).~/.wt/sessions.json→$XDG_STATE_HOME/wt/sessions.json.~/.wt/is now empty, remove the directory.Print one line per step so the user can audit what changed.
src/config.rsxdg_config_home()helper:std::env::var_os("XDG_CONFIG_HOME").filter(|p| !p.is_empty()).map(PathBuf::from).or_else(|| dirs::home_dir().map(|h| h.join(".config"))). Same shape forxdg_state_home()(.local/state).Config::load()andConfig::load_for_repo()to resolve the global path asxdg_config_home().join("wt/config.toml").$XDG_CONFIG_HOME/wt/config.toml→ legacy~/.wt/config.toml→ defaults. The existingload_layeredinfrastructure already supports multiple layers. If only the legacy location exists, log a one-line stderr notice the first time per process:wt: notice: reading config from legacy ~/.wt/config.toml; rerun ./install.sh to migrate.Config::ensure_wt_dirwithConfig::ensure_config_dir()andConfig::ensure_state_dir(). Update call sites.src/session.rsxdg_state_home().join("wt/sessions.json").~/.wt/sessions.json, with the same one-line notice on first read.README.md~/.config/wt/config.toml(note legacy is still readable).PATH../install.sh --systemand the migration behaviour.Acceptance criteria
~/.wt/present) puts the binary at~/.local/bin/wtand writes nothing under~/.wt/../install.sh --systeminstalls to/usr/local/bin/wt(withsudoif necessary).$XDG_CONFIG_HOME/wt/config.tomland$XDG_STATE_HOME/wt/sessions.jsonrespectively (defaults~/.config/wt/config.tomland~/.local/state/wt/sessions.json).~/.wt/{wt,config.toml,sessions.json}runs the newinstall.shand ends up with: files in the new locations, an empty (or removed)~/.wt/directory, and the legacyalias wt=...line removed from every shell rc that contained it.which wtin a fresh shell resolves to the new binary path, not an alias.install.shafter a successful migration is idempotent — no duplicate edits, no errors, exit 0.wtdirectly (without re-runninginstall.sh), the binary still finds and reads their legacy config and state, with a one-time stderr deprecation notice.XDG_CONFIG_HOMEandXDG_STATE_HOMEenv vars are honoured (verified by test).install.shdoes not write any shell-rc alias under any code path. If the install dir is not onPATH, it prints a single instruction line.~/.local/bin/wt, config at~/.config/wt/config.toml, state at~/.local/state/wt/sessions.json— i.e. same XDG paths as Linux, not~/Library/Application Support/. Documented in README.~/.local/bin/is not on$PATH,install.shprints a single instruction line and does not modify.zshrc/.bash_profile/config.fish.HOMEto a temp dir and seeding only the legacy paths.Out of scope
wt --uninstallflow.Blocked by
None — can start immediately.