Aliases that cd.
bro is a personal command alias manager. Save any command under a short name — including ones that change your directory, activate environments, or export variables. Works in bash, zsh, fish, and PowerShell (basic cmd.exe support, no completions).
curl -fsSL https://raw.githubusercontent.com/AdityaSawant0912/bro-cli/master/install.sh | bash source ~/.bashrc
Shell aliases have a fatal flaw.
You've written this before:
alias proj="cd ~/myapp && code ."
And discovered it silently fails. cdruns in a subshell. Your working directory doesn't change. You write a shell function instead. Then you switch to zsh. Or fish. Or PowerShell at work.
Now you're maintaining three separate config files with subtly different syntax and none of them handle arguments cleanly.
There's a better way.
# What you wrote alias proj="cd ~/myapp && code ." # What happened $ proj $ pwd /home/you ← directory didn't change # What you wanted $ bro add proj "cd ~/myapp && code ." $ bro proj ~/myapp $ ← it works
Why I built this
bro started as a Python CLI I used to manage my own shell aliases. It worked fine until I tried to alias something that needed to change my shell's state, like cd into a project folder or activate a virtualenv. A subprocess can't reach back and modify the shell that spawned it, so every alias that needed to move me somewhere or change my environment just silently failed.
The fix was to stop trying to do the work inside the CLI and instead have bro emit the shell command as text, then let the shell itself evaluate it. bro doesn't cd for you. It tells your shell to cd, and your shell does it. That one shift, emit-and-eval instead of execute-and-forget, is what let this work identically across bash, zsh, fish, and PowerShell without four different codebases.
I rewrote it in Rust to get a single static binary with no runtime dependency, and built the shell integration around a Shell trait so adding a new shell later is a matter of implementing one interface, not re-architecting the tool.
A thin wrapper. A big difference.
bro doesn't run your commands in a subshell. It emits shell code that your shell evaluates directly in the live session — the same mechanism as sourcing a file.
bro proj ↓ resolves "proj" → "cd ~/myapp && code ." ↓ prints to stdout ↓ your shell wrapper eval's it in-place ✓ directory changes. VS Code opens.
One line added to your .bashrc or $PROFILE. Everything else is automatic.
Everything you need. Nothing you don't.
shell state
cd, export, source, conda activate — all persist.
bro auto-detects stateful commands. Override with --shell or --no-shell.
bro add proj "cd ~/myapp && code ." --shell bro proj # your CWD changes. your editor opens.
every shell
One alias file. bash, zsh, fish, PowerShell, cmd.exe.
Same aliases, same behavior, regardless of which shell you're in.
bro init bash # → eval "$(bro init bash)" bro init powershell # → paste into $PROFILE bro init fish # → bro init fish | source
arg placeholders
Stop copy-pasting. Stop forgetting namespace flags.
{} auto-positional. {1}, {2} explicit. {name} from --name value. Falls back to appending when no placeholders present.
bro add deploy "kubectl apply -f {} -n {ns}"
bro deploy manifests/app.yaml --ns staging
# → kubectl apply -f manifests/app.yaml -n stagingfuzzy picker
Forgot the name? Just hit bro.
Type to filter. Arrow keys to move. Enter to run. Uses fzf if installed, built-in fuzzy select otherwise.
$ bro
deploy kubectl apply -f {} -n {ns}
gs git status
▶ proj cd ~/myapp && code .
nuke kubectl delete ns {ns}project aliases
Scoped to the repo. Not your whole system.
Drop a .bro file in any project root. bro walks up from your CWD to find it. Nothing leaks outside the project tree.
[aliases] build = "docker compose up --build" test = "pytest -x --tb=short"
plain TOML
Hand-editable. Diff-friendly. Version-controllable.
No binary format. No database. Symlink it from your dotfiles repo and every machine stays in sync.
[aliases]
gs = "git status"
proj = { cmd = "cd ~/myapp && code .", shell = true }
deploy = { cmd = "kubectl apply -f {} -n {ns}", tags = ["k8s"] }
nuke = { cmd = "kubectl delete ns {ns}", confirm = true }More capabilities.
Tags
bro list --tag k8s filters aliases by category.
Usage stats
bro list --by-usage surfaces what you actually run. bro info gs shows run count and last-used.
Confirm guard
confirm = true prompts y/N before running destructive commands.
Chaining
bro run -c build,test,deploy sequences aliases sharing shell state.
Dry-run
bro -n gs prints the resolved command without executing it.
Edit in place
bro edit gs opens the store in $EDITOR, jumps to the alias line, re-validates TOML on save.
Up in 60 seconds.
curl -fsSL https://raw.githubusercontent.com/AdityaSawant0912/bro-cli/master/install.sh | bash
source ~/.bashrc
# Add aliases
bro add gs "git status"
bro add proj "cd ~/myapp && code ." --shell
bro add deploy "kubectl apply -f {} -n {ns}" --tag k8s
bro add nuke "kubectl delete ns {ns}" --confirm --tag k8s
# Use them
bro gs
bro proj # CWD changes ✓
bro deploy app.yaml --ns prod
bro # fuzzy pickerPrefer to build it yourself?
Skip the install script and compile from source. Requires Rust 1.75+.
git clone https://github.com/AdityaSawant0912/bro-cli cd bro-cli cargo build --release # binary at target/release/bro (bro.exe on Windows) # wire up the shell wrapper eval "$(./target/release/bro init bash)"
Shell support
| Shell | Wrapper | State | Completion |
|---|---|---|---|
| bash | eval "$(bro init bash)" | ✓ | ✓ |
| zsh | eval "$(bro init zsh)" | ✓ | ✓ |
| fish | bro init fish | source | ✓ | ✓ |
| PowerShell | Invoke-Expression (& bro init powershell | Out-String) | ✓ | ✓ |
| cmd.exe | bro init cmd → save as bro.bat | ✓ | — |
FAQ
Why not just use shell functions?
Functions work but they're shell-specific. You'd maintain separate versions for bash, zsh, fish, and PowerShell. bro gives you one config that works everywhere.
How is this different from direnv, zoxide, or asdf?
They solve adjacent but different problems. direnv loads environment variables when you cd into a directory — bro lets you define named, reusable commands that can cd anywhere, not just react to entering a folder. zoxide is a smarter cd based on frecency — bro doesn't try to guess where you want to go, you name it once and call it by that name. asdf manages tool/runtime versions — bro doesn't touch versions at all, it's purely about turning multi-step shell workflows into one alias. The short version: if you want "when I'm in this folder, do X," use direnv. If you want "jump to a shell state I define, from anywhere, by name," bro is that.
Does it handle conda activate, nvm use, pyenv shell?
Yes. bro auto-detects these multi-word stateful patterns. You don't need --shell.
What if I don't have Rust installed?
You don't need Rust at all — install.sh / install.ps1 fetch a prebuilt binary for your platform (Linux, macOS Intel/Apple Silicon, Windows) from the latest GitHub Release. Rust is only needed as a fallback if no matching binary exists.
How do I sync aliases across machines?
The store is ~/.config/bro/aliases.toml — a plain file. Symlink it from your dotfiles repo, Dropbox, or iCloud Drive. Project aliases live in .bro files you commit to the repo itself.
What if an alias name collides with a subcommand like list or add?
Use bro run list — the explicit run subcommand is the escape hatch.
Does it work in WSL?
Yes. The Linux binary runs in WSL, wires into your WSL bash/zsh/fish, and works independently of the Windows side.