Skip to content

Shell

Fish is the primary interactive shell on Unix-like hosts, Bash is the small fallback, and PowerShell is the native Windows peer. The goal is shared muscle memory for history, movement, prompt shape, and completion without forcing every shell to behave identically.

History

KeyBash/readlineFishPowerShell
UpPrefix history searchAtuin workspace search when availablePrefix history search
DownPrefix history search forwardFish forward search/pager behaviorPrefix history search forward
Ctrl-PPrefix history searchPrefix history searchPrefix history search
Ctrl-NPrefix history search forwardPrefix history search forwardPrefix history search forward
Ctrl-RAtuin or reverse searchAtuin global searchAtuin or reverse search

Readline includes /etc/inputrc first so host defaults such as terminal key sequences stay available, then the managed .inputrc overrides the keys this repo owns. Atuin owns broad interactive search where it is installed. The plain prefix search bindings remain local shell history controls, which keeps a fast path for repeating commands that start with the text already typed.

Editing

Tab uses menu-style completion where the shell supports it. Ctrl-Left and Ctrl-Right are the managed word movement contract for Bash/readline and PowerShell, with Fish using its standard terminal bindings. Delete remains an explicit forward-delete key in readline and PSReadLine. Bash enables bracketed paste through readline; Fish and PSReadLine keep their native paste behavior.

Prompt

Starship owns prompt rendering in all managed shells. The active prompt shows host, directory, Git and tool modules, and command duration when relevant. The clock module stays disabled; terminal scrollback already provides enough temporal context, and the extra timestamp made the prompt noisier across terminal emulators.

Fish intentionally enables Starship's transient prompt support: completed prompt lines collapse to the prompt character so scrollback stays compact. Bash and PowerShell keep completed prompts expanded. This difference is deliberate; the shared contract is that the active prompt shows the same operational context and Fish keeps history visually quieter after a command is submitted.

Project Environments

Mise and direnv have separate jobs:

text
mise    tool versions and PATH
direnv  project env vars, secret pointers, feature flags, local exports

Project tool versions belong in .mise.toml or language-native project files that mise understands. Direnv .envrc files should load local env data and set project variables without activating tools or rewriting PATH.

Good .envrc examples:

bash
source_up_if_exists .envrc.shared
dotenv_if_exists .env.local

export API_BASE_URL=http://127.0.0.1:3000
export FEATURE_PREVIEW=1
export RUFF_CACHE_DIR="$(project_cache_dir ruff)"
layout_tmp

For nested project directories, keep shared non-secret exports in an ancestor .envrc.shared and call source_up_if_exists .envrc.shared from the nested .envrc. Keep machine-local or secret values in .env.local, which should remain untracked.

Avoid using .envrc for tool or PATH activation:

bash
use mise
eval "$(mise activate bash)"
PATH_add ~/.local/share/mise/shims
layout python

If a project needs a different Node, Python, uv, pnpm, Ruff, or CLI version, pin it in .mise.toml and let the shell's mise activation own the resulting PATH.

Local Overrides

Machine-local shell quirks belong in ~/.config/dotfiles/local.d/, which is ignored by chezmoi. Bash, Fish, and PowerShell source their matching local files last, after managed startup has finished.

Interactive shells load all matching shell files in lexical order. Noninteractive startup loads only files that include the exact marker # dotfiles: noninteractive-safe, so script-safe environment tweaks can be kept separate from interactive aliases and functions.

See local overrides for the file names and examples.