Per-shell context switcher for zsh, bash, and fish. Swap sets of env vars,
aliases, and shell functions ("projects") in and out of your current shell,
manually or automatically as you cd around.
$ shctx load acme # env vars, aliases, functions of "acme" are now live
$ cd ~/git/acme-infra # auto-switches if that dir has a default
$ shctx reset # back to directory/global defaults
curl -fsSL https://raw.githubusercontent.com/adrsimon/shctx/main/install.sh | shThis installs the binary to ~/.local/bin, detects your login shell, and
adds one line to its rc file inside a # >>> shctx >>> block:
eval "$(shctx init zsh)" # .zshrc; or init bash in .bashrc
shctx init fish | source # fish/conf.d/shctx.fishSet SHCTX_SHELL=zsh|bash|fish before the install to override the detection.
From source: go install github.com/adrsimon/shctx@latest, then add the
line for your shell yourself.
| Command | Effect |
|---|---|
shctx load NAME |
Load a project into this shell (sticky until reset) |
shctx reset |
Drop the manual load and re-resolve by directory/global default |
shctx reload |
Re-apply the current project after editing its files |
shctx current |
Show the active project |
shctx list |
List all projects |
shctx new NAME |
Scaffold a new project and cd into its directory |
shctx rm NAME |
Delete a project and any defaults pointing to it |
shctx default |
Show the global default and directory mappings |
shctx default global NAME |
Fallback project when no directory matches |
shctx default dir NAME |
Auto-load NAME in the current dir and its subdirs |
shctx default global/dir --unset |
Remove the global default or the current dir mapping |
Resolution order: manual load (until reset), then the deepest matching
directory default, then the global default, then nothing. Directory matching
walks up from $PWD, so a default registered on ~/git/acme also applies in
~/git/acme/front/src. State lives in SHCTX_* env vars, so each terminal
has its own independent context.
A project is a directory under ~/.config/shctx/projects/NAME/:
manifest.toml # env vars, aliases, PATH entries
functions.zsh # shell functions, sourced verbatim
functions.bash # bash variant, optional
functions.fish # fish variant, optional
functions.sh # shared fallback (zsh and bash only, fish is not POSIX)
# manifest.toml
path_prepend = ["~/acme/tools/bin"]
[env]
ACME_API = "https://api.acme.dev"
[aliases]
alog = "tail -f /var/log/acme.log"# functions.zsh
deploy() {
echo "deploying with $ACME_API"
}Manifests often hold secrets, so project files are created with 0600
permissions and loose permissions are tightened on load.
Everything a project introduces is torn down when you switch away. Reserved
vars (PATH, HOME, EDITOR, PS1, ...) are stripped at load time with a
warning. Extend the list via extra_denylist in ~/.config/shctx/config.toml.
Setting PATH wholesale is blocked, but path_prepend and path_append
add individual entries on load and remove exactly those entries on switch.
A leading ~ is expanded. Don't list a directory that must stay on PATH
after the context unloads: teardown removes every occurrence of it.
go test ./... # unit tests
./scripts/e2e.zsh # end-to-end test in a real, isolated zsh
./scripts/e2e.bash # same for bash
./scripts/e2e.fish # same for fish (needs fish installed)Releases are built with goreleaser. install.sh
pulls shctx_<os>_<arch>.tar.gz from the latest GitHub release and verifies
its checksum.