project ❯ gti status
zsh: command not found: gti
project ❯ welp
welp git status ↵ run · esc nah
Why welp
thefuck is one of the most-loved command-line tools ever made. It has also had no release since January 2022, and it breaks on Python 3.12 and newer (No module named 'imp'). welp keeps what made it great and quietly fixes the rest.
| thefuck | welp | |
|---|---|---|
| Needs Python | yes | no, one small binary |
| Last release | January 2022 | active |
| Speed | starts Python every time | starts in about 3 ms, a typical fix in about 20 ms |
| Runs a failed command again to read its output | every command | never rm, mv, dd, editors, ssh and the like |
fuck --yeah |
yes | yes |
| Moves your setup over | welp migrate keeps your alias and settings |
|
| Your own rules | Python files | TOML files, no code needed |
| Rules | 168 | all 168, same names, plus did_you_mean; the 7 that can destroy something are opt-in |
| Personality | a memorable name | a shrug mascot, three voices, milestones, welp stats |
| Shells | bash, zsh, fish, tcsh, PowerShell | bash, zsh, fish, tcsh, PowerShell |
| Install | pip |
brew, cargo, or a download for macOS, Linux and Windows |
Speed measured on a MacBook Pro with welp fix; your numbers will vary.
Everything thefuck does, welp does
- All 168 rules, under the same names, so
exclude_rulesandrulesfrom your thefuck settings keep working - bash, zsh, fish, tcsh and PowerShell, each tested in CI by mistyping a command and running
fuck --yeah -
fuck,fuck --yeah/-y, and picking between fixes with the arrow keys - macOS, Linux and Windows
- Your own rules (TOML here instead of Python)
- Your settings:
welp migratemovessettings.py, your alias and your shell config over in one go
Meet the voice
welp is lowercase, low-key and a little dry. It shrugs when it has nothing, has a line for particular fixes, marks milestones and notices when you keep making the same typo:
project ❯ welp
welp git status
gti → git, 10 times now. we don't have to talk about it.
project ❯ welp
welp ¯\_(ツ)_/¯ nothing to fix here. that one's all you.
Pick a voice in your settings: chill (the default, a line now and then), spicy (teases you every time) or quiet (just the fix).
welp stats shows what it remembers:
project ❯ welp stats
welp your mistakes, lovingly kept
fixes 142 since oct 22, 2026
favorite rule git_push (38)
top typos gti → git (14) · sl → ls (5) · pyhton → python (3)
verdict you and git have a thing going on.
It keeps only counts, rule names and the mistyped words, never your commands, in ~/.local/state/welp/stats.toml. Set remember = false to turn it off.
Install
Or grab a prebuilt binary for macOS, Linux or Windows from the releases page, unpack it and put welp somewhere on your PATH.
Set up
Add one line to your shell config, then open a new terminal:
| Shell | Line | File |
|---|---|---|
| zsh | eval "$(welp init zsh)" |
~/.zshrc |
| bash | eval "$(welp init bash)" |
~/.bashrc |
| fish | welp init fish | source |
~/.config/fish/config.fish |
| tcsh | eval `welp init tcsh` |
~/.tcshrc |
| PowerShell | Invoke-Expression (& welp init powershell | Out-String) |
your $PROFILE |
Prefer another word? Add --alias, for example eval "$(welp init zsh --alias oops)".
Coming from thefuck
It comments out the thefuck lines in your shell config (bash, zsh, fish, tcsh or PowerShell, with a backup next to each file), adds welp in their place with your alias, and turns ~/.config/thefuck/settings.py into ~/.config/welp/config.toml, including your exclude_rules and any extra rules you turned on.
Rules
welp has all of thefuck's rules (168 of them, with the same names) plus did_you_mean, which uses the fix a tool suggests itself. The everyday ones:
| Rule | What it fixes |
|---|---|
no_command |
A mistyped program name: gti → git |
git_not_command |
A mistyped git subcommand: git comit → git commit |
git_push |
Sets the upstream when a new branch has none |
git_stash |
Stashes your changes when a checkout or pull needs a clean tree |
sudo |
Runs the command again with sudo after "permission denied" |
cd_correction |
cd projcts → cd projects |
mkdir_p |
Adds -p when parent folders are missing |
did_you_mean |
Uses the fix a tool suggests: cargo, npm, pip, brew, gh and more |
Seven rules are off by default because their fix deletes, force-pushes, kills or removes something the typed command didn't ask to: git_push_force, git_tag_force, port_already_in_use, rm_root, ssh_known_hosts, django_south_ghost and docker_image_being_used_by_container. Turn one on with enable_rules. welp rules lists every rule and which are off.
| Rule | What it fixes |
|---|---|
adb_unknown_command |
Fixes a mistyped adb command |
ag_literal |
Searches ag for a literal string with -Q after a bad regex |
apt_get |
Installs the missing program the system names, then runs it |
apt_get_search |
Searches with apt-cache, since apt-get has no search |
apt_invalid_operation |
Fixes a mistyped apt operation |
apt_list_upgradable |
Lists the upgradable packages apt mentions |
apt_upgrade |
Upgrades after listing the upgradable packages |
aws_cli |
Takes the aws command the cli suggests |
az_cli |
Takes the az command the cli suggests |
brew_cask_dependency |
Installs the cask a formula needs first |
brew_install |
Installs the formula brew suggests |
brew_link |
Previews what brew link --overwrite would replace |
brew_reinstall |
Reinstalls a formula that is already installed and up to date |
brew_uninstall |
Adds --force to remove every installed version |
brew_unknown_command |
Fixes a mistyped brew command |
brew_update_formula |
Upgrades the formula, since brew update only updates brew |
cargo |
Builds when cargo is run without a command |
cargo_no_command |
Takes the cargo command it suggests |
cat_dir |
Lists a folder instead of trying to cat it |
cd_correction |
Fixes a misspelled folder in a cd path |
cd_cs |
cs to cd, the keys sit side by side |
cd_mkdir |
Creates the folder you tried to cd into |
cd_parent |
cd.. to cd .. |
chmod_x |
Makes a script executable before running it |
choco_install |
Installs the .install package of the same name |
composer_not_command |
Takes the composer command it suggests |
conda_mistype |
Takes the conda command it suggests |
cp_create_destination |
Creates the destination folder, then copies or moves into it |
cp_omitting_directory |
Adds -r to copy a folder |
cpp11 |
Adds -std=c++11 when the compiler needs it |
did_you_mean |
Uses the fix a tool suggests (cargo, npm, pip, brew, gh and more) |
dirty_untar |
Extracts a tar archive into a folder of its own |
dirty_unzip |
Extracts a zip with many files into a folder of its own |
django_south_ghost |
Deletes ghost migrations from the database with --delete-ghost-migrations (off by default) |
django_south_merge |
Adds --merge to a South migration that ran out of order |
dnf_no_such_command |
Fixes a mistyped dnf command |
docker_image_being_used_by_container |
Force-removes the container that uses the image, then removes it (off by default) |
docker_login |
Logs in to docker first |
docker_not_command |
Fixes a mistyped docker command |
dry |
Drops a word you typed twice |
fab_command_not_found |
Fixes a mistyped fabric task |
fix_alt_space |
Swaps a non-breaking space (Alt+Space) for a normal one |
fix_file |
Opens $EDITOR at the line an error points to, then runs the command again |
gem_unknown_command |
Fixes a mistyped gem command |
git_add |
Stages a file git does not know yet, then runs the command again |
git_add_force |
Adds --force to stage a file your .gitignore covers |
git_bisect_usage |
Uses the git bisect subcommand that looks like the one you typed |
git_branch_0flag |
Turns 0a, 0d, 0v and friends into -a, -d, -v after git branch |
git_branch_delete |
Force-deletes a branch that is not merged |
git_branch_delete_checked_out |
Leaves the branch, then deletes it |
git_branch_exists |
Recreates or switches to a branch that already exists |
git_branch_list |
Deletes the branch called list that git branch list made, then lists branches |
git_checkout |
Creates the branch when it does not exist yet |
git_clone_git_clone |
Drops the second git clone from a pasted clone command |
git_clone_missing |
Adds git clone in front of a pasted repository address |
git_commit_add |
Stages changed files when a commit has nothing staged |
git_commit_amend |
Amends the commit you just made |
git_commit_reset |
Undoes the last commit and keeps its changes |
git_diff_no_index |
Adds --no-index to diff two files git does not track |
git_diff_staged |
Adds --staged to diff what is staged |
git_fix_stash |
Fixes a misspelled git stash subcommand, or saves with a message |
git_flag_after_filename |
Moves a flag in front of the file name |
git_help_aliased |
Opens help for the command an alias stands for |
git_hook_bypass |
Adds --no-verify to skip git hooks on am, commit or push |
git_lfs_mistype |
Uses the git lfs command git-lfs suggests |
git_main_master |
Swaps main and master when the branch is not there |
git_merge |
Merges the remote branch git suggests |
git_merge_unrelated |
Adds --allow-unrelated-histories to a merge git refuses |
git_not_command |
Fixes a mistyped git subcommand |
git_pull |
Sets the tracking branch, then pulls |
git_pull_clone |
Clones the repository you tried to pull outside of any repository |
git_pull_uncommitted_changes |
Stashes your changes around the pull |
git_push |
Sets the upstream when a new branch has none |
git_push_different_branch_names |
Pushes the way git says to when the branch names differ |
git_push_force |
Pushes with --force-with-lease when the remote is ahead (off by default) |
git_push_pull |
Pulls first when a push is rejected, then pushes |
git_push_without_commits |
Makes the first commit, then pushes |
git_rebase_merge_dir |
Continues, skips or aborts the rebase that is already going |
git_rebase_no_changes |
Skips the patch a rebase finds empty |
git_remote_delete |
git remote remove, not delete |
git_remote_seturl_add |
Adds the remote when set-url finds none by that name |
git_rm_local_modifications |
Adds --cached to keep a file with local changes |
git_rm_recursive |
Adds -r to remove a folder |
git_rm_staged |
Adds --cached to keep a file with staged changes |
git_stash |
Stashes your changes, then runs the command again |
git_stash_pop |
Stages the changes that block git stash pop, pops, then unstages |
git_tag_force |
Adds --force to move a tag that already exists (off by default) |
git_two_dashes |
Puts the second dash on a long option, as git asks |
go_run |
Adds .go to the file go run was given |
go_unknown_command |
Fixes a mistyped go command |
gradle_no_task |
Fixes a mistyped gradle task |
gradle_wrapper |
Uses ./gradlew when gradle itself is not installed |
grep_arguments_order |
Puts the file after the pattern in grep |
grep_recursive |
Adds -r to search a folder |
grunt_task_not_found |
Fixes a mistyped grunt task |
gulp_not_task |
Fixes a mistyped gulp task |
has_exists_script |
Runs a script in this folder with ./ |
heroku_multiple_apps |
Names one of the apps in the git remotes with --app |
heroku_not_command |
Runs the command heroku says it meant |
history |
Tries the closest command from your history |
hostscli |
Fixes a misspelled hostscli command, or lists the websites it knows |
ifconfig_device_not_found |
Uses an interface ifconfig lists instead of the one you typed |
java |
Drops .java when running a class |
javac |
Adds .java to the file javac was given |
lein_not_task |
Takes the lein task it suggests |
ln_no_hard_link |
Adds -s when a hard link to a folder is not allowed |
ln_s_order |
Puts the link name last in ln -s |
long_form_help |
Uses --help, or the help command the tool suggests |
ls_all |
Adds -A when ls shows nothing, to see hidden files |
ls_lah |
Lists with -lah |
man |
Tries another man section, or the --help of the tool |
man_no_space |
Adds the missing space after man |
mercurial |
Uses the hg command hg suggests |
missing_space_before_subcommand |
Adds the missing space after a program name: gitbranch to git branch |
mkdir_p |
Adds -p when parent folders are missing |
mvn_no_command |
Gives mvn a goal to run |
mvn_unknown_lifecycle_phase |
Fixes a mistyped mvn lifecycle phase |
nixos_cmd_not_found |
Installs the program with nix-env, as NixOS suggests, then runs it |
no_command |
Fixes a mistyped program name |
no_such_file |
Creates the folder cp or mv could not find, then runs the command again |
npm_missing_script |
Fixes a mistyped script name for npm run |
npm_run_script |
Uses npm run-script for a script from package.json |
npm_wrong_command |
Fixes a mistyped npm command |
omnienv_no_such_command |
Fixes a mistyped goenv, nodenv, pyenv or rbenv command |
open |
Opens a web address with http://, or creates the missing file or folder |
pacman |
Installs the package that has the missing program, then runs it |
pacman_invalid_option |
Capitalizes pacman's operation flag: -s to -S |
pacman_not_found |
Fixes a package name pacman does not know |
path_from_history |
Swaps a path that is not there for a longer one from your history |
php_s |
Uses -S, the built-in server flag, in place of -s |
pip_install |
Installs with --user (then sudo) after a permission error |
pip_unknown_command |
Takes the pip command it suggests |
port_already_in_use |
Kills the process that holds the port, then runs the command again (off by default) |
prove_recursively |
Adds -r to prove to look inside folders |
python_command |
Runs a .py script with python |
python_execute |
Adds .py to the script python was given |
python_module_error |
Installs the missing Python module with pip, then runs the command |
quotation_marks |
Makes mismatched quotes match: 'text" to "text" |
rails_migrations_pending |
Runs the pending migrations rails asks for, then the command |
react_native_command_unrecognized |
Fixes a mistyped react-native command |
remove_shell_prompt_literal |
Drops the $ copied from a documentation code block |
remove_trailing_cedilla |
Drops a ç typed at the end |
rm_dir |
Adds -r to remove a folder |
rm_root |
Adds --no-preserve-root to rm on / (off by default) |
scm_correction |
Uses git or hg, whichever repository this folder holds |
sed_unterminated_s |
Closes the s/// command with a slash |
sl_ls |
sl to ls |
ssh_known_hosts |
Removes the changed host key with ssh-keygen -R, then connects again (off by default) |
sudo |
Runs the command again with sudo |
sudo_command_from_user_path |
Keeps your PATH under sudo for a program only you can find |
switch_lang |
Retypes a command written in another keyboard layout |
systemctl |
Puts the action before the service in systemctl |
terraform_init |
Runs terraform init first |
terraform_no_command |
Takes the terraform command it suggests |
tmux |
Picks the full tmux command out of the ones it says it could be |
touch |
Creates the missing folders, then the file |
tsuru_login |
Logs in to tsuru first |
tsuru_not_command |
Takes the tsuru command it suggests |
unknown_command |
Uses the command a tool says you probably meant (hdfs and others) |
unsudo |
Drops sudo from a command that refuses to run as root |
vagrant_up |
Starts the machine with vagrant up first |
whois |
Asks whois about the domain instead of the whole address |
workon_doesnt_exists |
Fixes a mistyped virtualenv name, or creates it with mkvirtualenv |
wrong_hyphen_before_subcommand |
Swaps the hyphen in git-log style commands for a space |
yarn_alias |
Takes the yarn command it suggests |
yarn_command_not_found |
Fixes a mistyped yarn command |
yarn_command_replaced |
Runs the command yarn says replaced the old one |
yarn_help |
Opens the documentation page yarn help points to |
yum_invalid_operation |
Fixes a mistyped yum operation |
Settings
~/.config/welp/config.toml (or $XDG_CONFIG_HOME/welp/config.toml):
= true # false runs the first fix straight away
= 3 # seconds to wait when reading a command's output
= 15
= ["lein", "react-native", "gradle", "./gradlew", "vagrant"]
= ["rm", "mv", "dd", "vim", "ssh"] # never run these twice (the full default list is longer)
= []
= [] # opt into the 7 rules that are off by default
= "chill" # chill, spicy or quiet
= true # false stops welp stats from counting
Set NO_COLOR to drop the colours; welp keeps the words.
Your own rules
Teach welp a fix without writing code: drop a TOML file in ~/.config/welp/rules/.
# ~/.config/welp/rules/please.toml
= "please"
= "please → sudo"
= '^please (?P<rest>.+)$'
= "sudo {rest}"
A rule can check the program, a regex on the command (script) and a regex on what it printed (output). Its fix (or a list of fixes) can use {script} and any named group from those regexes. Your fixes are offered first; add first = false to offer them after welp's own. welp rules lists yours and says what's wrong with any file it can't read.
Adding a rule to welp
A rule is a name, a check and a fix, in src/rules/. Each comes with a test that feeds it a command and the output a real tool printed:
Rule ,
Run cargo test before opening a pull request.
The demo above is a real session: demo/record.sh sets up a throwaway repo and types the commands into zsh.
Support
If welp saves you some typing, you can buy me a coffee or come hang out on Discord.
Credits
welp's rules are ports of thefuck's, by Vladimir Iakovlev and its contributors (MIT). welp is not affiliated with thefuck.
License
MIT