Skip to main content

The Bleep Version MIT License

The maintained successor to The Fuck.

Type the command wrong. Type bleep. Run the right one.

The Bleep correcting a mistyped command

Get it

curl -fsSL https://raw.githubusercontent.com/stamparm/thebleep/master/install.sh | sh

That picks up whichever of uv, pipx or pip you already have, and prints the one line to add to your shell's startup file. Prefer to do it yourself:

uv tool install thebleep          # or: pipx install thebleep
thebleep --alias-loader >> ~/.bashrc

Open a new shell, and the next time you mistype something, type bleep. The long version, including the muscle memory you already have:

thebleep --alias-loader fuck >> ~/.bashrc

Why not just The Fuck

Because the idea deserves better than its last release. The Fuck 3.32 is from January 2022: it cannot start on Python 3.12 or newer, over three hundred issues are open on it, and a good number of its rules quietly stopped matching when the tools they correct changed what they print. The Bleep is the same tool, maintained — and several times quicker about it.

Same machine, same Python 3.11, 30 runs each, medians:

                               % of The Fuck's time  The Fuck  The Bleep  faster
Open a shell                     ██▌░░░░░░░░░░░░░░░    206 ms      29 ms    7.1×
Correct a mistyped command       ████▌░░░░░░░░░░░░░    240 ms      60 ms    4.0×
Correct inside a git repository  ████░░░░░░░░░░░░░░    248 ms      54 ms    4.6×
Correct when nothing matches     ███▉░░░░░░░░░░░░░░    333 ms      72 ms    4.6×
Correct a slow command *         ████████████▍░░░░░    816 ms     562 ms    1.5×
Correct after 1 MB of output     ▋░░░░░░░░░░░░░░░░░    3.25 s     114 ms   28.4×

* that command sleeps for half a second. Both tools have to sit through it to read what it printed, so this row is mostly the sleep.

Opening a shell is worth a second look: that row is eval "$(thebleep --alias)" in your rc, which starts a Python interpreter every time. Use the loader instead and opening a shell defines a shell function and runs no Python at all — the 29 ms goes, and what is left is too small to measure honestly against the noise in shell startup.

Those are Linux numbers, on the machine named in the result file. Windows and PowerShell are exercised in CI on every push; On Windows is about what makes a correction expensive there and what was done about it.

The harness is bench/, the run these numbers come from is bench/results/final.json, and the block above is written from that file by bench/chart.py. Reproduce it, and read where the time went.

The rest of the reasons:

  • Python 3.9 through 3.14, tested on Linux, macOS and Windows on every one of them — and Bash, Zsh, Fish, tcsh and PowerShell as before, with Nushell added. Supported everything.
  • 30 items from The Fuck's backlog are fixed here, issues and pull requests, four of them command injections — plus the rules that had rotted against current git, npm, docker, cargo, brew, gem, az, gradle and terraform. What's fixed.
  • It asks before running your previous command a second time. Reading what your command printed used to mean running it again, side effects and all. Safe by default.
  • Press tab to edit the correction instead of running it. The suggestion lands in your own command line, with the cursor at the end of it, and nothing runs until you press return. Edit before you run.
  • Press ? to be told why. Which rule made the suggestion, what it saw, and whether accepting it does anything besides run the command. Why am I being told this.
  • thebleep --doctor answers the questions a bug report usually starts with, in one screen you can paste anywhere. Diagnostics.
  • Nothing to relearn. The same rules and settings, and the same fuck alias if you want it; seven rules are deliberately less eager, all in the direction of doing only what they say. Coming from The Fuck.

The Bleep is based on the original codebase by Vladimir Iakovlev and its contributors; their work and history remain fully credited.

Contents

  1. Safe by default
  2. Edit before you run
  3. Why am I being told this
  4. thebleep --doctor
  5. Coming from The Fuck
  6. What's fixed
  7. Supported everything
  8. Installation
  9. Updating
  10. Uninstall
  11. How it works
  12. Creating your own rules
  13. Settings
  14. Third-party packages with rules
  15. Experimental instant mode
  16. Performance
  17. Developing
  18. License

Safe by default

The Bleep asks before running a correction. In a non-interactive environment (pipe, subprocess or CI), it does not silently apply the first suggestion; use --yes when you explicitly want automatic application.

Reading the previous command

To suggest a fix, The Bleep needs to know what your command printed — and a shell keeps no record of that. The only way to find out is to run the command again, which means anything it changed changes twice:

$ deploy production
deploy: missing --confirm
$ bleep
deploy production has to run again to be read, and anything it changes will
change twice. Run it? [y/N]

So it asks first. It skips asking in two cases: the program is not there to be found, so nothing runs either time (gti status), or the program is one of a short list that only ever read, whatever they are asked to do (ls, cat, grep). That second one is a judgement about the name, and a name is not a proof about the program a PATH lookup will find — what makes it a reasonable one is that the same program under the same name ran a moment ago, when you typed it. It is deliberately not a list of dangerous commands: such a list only declares the ones nobody thought of to be safe.

Where nobody can be asked — a pipe, a subprocess, CI — the answer is no, and the correction is attempted from the command alone.

Two ways to stop being asked:

  • Record the output as it happens. Experimental instant mode reads what scrolled past instead of running anything again, so the question never comes up. This is the better answer if your shell supports it.
  • confirm_replay = False in your settings, or --yes for a single run, which restores The Fuck's behaviour of running the previous command again without asking.

Edit before you run

A suggestion is often ninety-five percent of what you wanted. Press tab instead of enter and it is handed to you in your own command line to finish:

$ git chekout featuer
git: 'chekout' is not a git command. See 'git --help'.
$ bleep
git checkout feature [enter/↑/↓/tab=edit/?/ctrl+c/esc]

tab, and the next thing you see is your own prompt, with the cursor after the last character:

$ git checkout feature█

From there it is an ordinary command line: edit it, or press return to run it, or ctrl+c to throw it away. Nothing has run yet. bleep --edit (or -e) makes that the behaviour of enter too, and edit = True in your settings makes it permanent — a mode where The Bleep never runs anything, it only writes your next command for you.

The arrow keys still walk the other suggestions, so you can pick the one worth editing before you edit it.

Which shells

The correction goes into the line editor through whatever the shell offers for exactly that. There is no fallback for the shells that offer nothing: the trick that would work everywhere is TIOCSTI, which pushes characters into another process's terminal as though they had been typed. Modern Linux can refuse it outright, and does by default — for good reasons that apply here too.

Shell How What you get
Zsh print -z your next prompt, already filled in
Fish commandline --replace your next prompt, already filled in
Nushell ≥ 0.87 commandline edit --replace every correction, always
Bash ≥ 4.0 read -e -i a readline prompt, already filled in
PowerShell PSConsoleReadLine::AddToHistory press to bring it up
Bash 3.2 (macOS system bash) not offered
tcsh not offered

Bash is the one that is close rather than exact. It has no way to write the next prompt's buffer, so what you get is readline itself — your keymap, your history, your editing keys — on a line that already holds the correction, and the prompt is your own PS1. PowerShell's editing API belongs to a key handler and does nothing when called from a function, so there the correction becomes the newest history entry and one brings it up.

Where editing is not available, tab is not offered and does nothing; --edit says so and runs nothing rather than falling back to running the command. The prompt tells you which case you are in: if it says tab=edit, it works.

Editing does not fire a rule's side effect and does not touch your history. Both belong to a command that ran, and an edited one has not — your shell records whatever you finally submit, which is the command you actually chose.

Nushell

Nushell is the shell where this is not an option but the whole design, so it is worth saying plainly what happens: a correction always goes to your command line, and you press return to run it.

> gti status
Error: nu::shell::external_command
  × External command failed
> bleep
git status [enter/↑/↓/ctrl+c/esc]
> git status█

That is not a shortcoming worked around. Nushell has no eval, deliberately — it parses a script all the way through before running any of it, which is where most of what it can tell you about a pipeline comes from, and code that appears at run time cannot be parsed that way. nu -c '...' is not a substitute: it starts a second Nushell, so a corrected cd, mkdir -p x; cd x or $env assignment would happen to a process that immediately exits, and a correction that silently does nothing is worse than no correction. Writing it into your command line runs it in the session you are actually in.

Two smaller differences follow from the same place. and/or in Nushell are boolean operators and not command separators, so a chained correction is written try { git pull; git push }try stops at the command that failed, which is what && means. And the broken command is not removed from your history, since Nushell has no way to delete an entry; the corrected one is recorded normally when you submit it.

Nushell 0.87 or newer, which is where commandline edit arrived.

Why am I being told this

A correction is a command you are about to run, and "because a program said so" is a thin reason to run anything. Press ? at the prompt:

$ git chekout featuer
$ bleep
git checkout feature [enter/↑/↓/tab=edit/?/ctrl+c/esc]
  rule     git_not_command (bundled)
  matched  git, and output containing "is not a git command. See 'git --help'."
  read     what your command printed
git checkout feature [enter/↑/↓/tab=edit/?/ctrl+c/esc]

Two more lines appear when they apply: side effect, when accepting the suggestion does something besides run the command, and runs as, when the correction begins with sudo or doas. Having asked once, the arrow keys explain each suggestion as you walk them. bleep --explain starts that way, and explain = True in your settings makes it permanent.

Everything there is a fact about the rule rather than a description of it: its name, which of the three places its file came from, whether it declares that it needs your command's output, whether it has a side effect — and then the two that carry most of the meaning, the app it says it is about and the text it requires in the output, both read out of the rule's own match by the same extraction that decides which rules to load at all. Where several messages would have satisfied the rule, the one quoted is the one that is actually in your output.

Nothing reads a rule's body and tries to say in English what it means, and no rule had to be given a hand-written description for this to work — so a rule of your own, or one from a package, explains itself exactly as well as a bundled one does. A rule that works its condition out in a way this cannot read says so: matched a condition this rule works out for itself.

Back to Contents

thebleep --doctor

When something is not working, it is nearly always one of a dozen things, and every one of them is a fact about the machine rather than about the code — the alias is in a file this shell does not read, thebleep on PATH is an older copy in another virtualenv, the settings file has a typo so every setting in it was dropped, ~/.config/thefuck was never copied over. --doctor checks all of them at once:

$ thebleep --doctor
  The Bleep           4.0.0
  Python              3.12.3 (/usr/bin/python3)
  Platform            Linux 6.8.0 (x86_64)
  Shell               ZSH 5.9 (from TB_SHELL)
  Integration         alias loader in ~/.zshrc
  Executable          ~/.local/bin/thebleep
  On PATH             yes
  Config              ~/.config/thebleep/settings.py (2 set: priority, rules)
  Rules               174 bundled, 3 of your own
  Rule pack           ~/.cache/thebleep/rules-3-cb0d0d0a.pack (174 rules cached)
- Replayless capture  available, not switched on
                      See --enable-experimental-instant-mode.
  Editing             supported by this shell (tab at the prompt)

Everything looks good.

! marks something worth fixing and the advice sits under it; - is worth knowing. The exit status is non-zero when there is a !, so it is usable in a script.

It is safe to paste. A diagnostic ends up in an issue, so it says that a setting is set and not what it is set to, that an alias is defined and not what it expands to, which rules exist and not what is in them. Nothing is read out of the environment except the handful of names The Bleep itself defines, and those are reported as set or unset. Paths have your home directory folded back to ~, so your username does not travel either.

It changes nothing. No config directory is created, no settings file is written, no rule pack is built — a report that has to alter the machine before it can describe it is describing a different machine.

Back to Contents

Coming from The Fuck

Nothing is relearned. The rules, the settings and the flags are the ones you already know; the names changed and the config moved.

pip uninstall thefuck                       # optional, they coexist happily
cp -r ~/.config/thefuck ~/.config/thebleep  # settings.py and your own rules

Then swap the line in your startup file. Keeping the word you are used to is one argument:

thebleep --alias-loader fuck >> ~/.bashrc   # and delete the thefuck line

What to know:

  • THEFUCK_* environment variables are THEBLEEP_*. The names after the prefix are unchanged.
  • Config is $XDG_CONFIG_HOME/thebleep/settings.py, and your own rules go in $XDG_CONFIG_HOME/thebleep/rules. The settings themselves are the same, so the file copies straight over.
  • A rule of your own that imports thefuck.utils wants thebleep.utils. That is the whole of the port.
  • A rule package of your own is thebleep_contrib_* rather than thefuck_contrib_*.
  • The Bleep asks before running your previous command a second time. confirm_replay = False in your settings restores what you are used to, and Reading the previous command explains why you might not want to.

Seven rules behave differently on purpose, all in the same direction — what you agree to is what runs:

  • dirty_untar and dirty_unzip suggest extracting into a directory of their own, and no longer delete the files that were already unpacked. They could not tell an extracted file from one of yours under the same name, and their containment check was a string prefix that ../ walks straight out of.
  • ssh_known_hosts shows you the ssh-keygen -R it wants to run, in front of your command. It used to hand back your own command and remove the offending line behind it, so a man-in-the-middle warning disappeared with nothing to read.
  • rm_dir adds -r, not -rf. -r is enough to remove a directory; -f also silences the prompt for a write-protected file.
  • pip_install no longer falls back to sudo pip install.
  • python_module_error is off by default. An import name is not a distribution name — import yaml wants PyYAML — so the package it suggests installing is a guess, and a mistyped import makes it pip install <typo>. Ask for it with rules = ['DEFAULT_RULES', 'python_module_error'].
  • quotation_marks only fires when your command genuinely does not parse and swapping the quotes makes it parse. It used to fire whenever both kinds of quote appeared and rewrite them, so git commit -m "it's fine" became git commit -m "it"s fine".
Back to Contents

What's fixed

Every commit that fixes a reported problem names the issue it fixes, so this is git log --grep 'nvbn/thefuck#' rather than a claim in a README. Thirty upstream backlog items so far — half of them issues and half of them pull requests nobody merged — every one of them linked below, and the rest found by running the tools.

It starts on current Python. distutils was removed in 3.12 and The Fuck imports it, so it cannot run there at all; pkg_resources and imp were going the same way. All three are gone, Python 2 support went with them, and the suite runs on 3.9 through 3.14 on Linux, macOS and Windows.  #1499 #1610 #1552 #1479 #873

Four ways a command could be turned into a different command. A correction is text a shell then evaluates, and much of that text is copied out of somewhere you do not control — a tool's error message, a repository's branches, a package file's scripts — where shell syntax is perfectly legal: git will make you a branch called feature;rm -rf ~. Unquoted were the names the *_no_command rules read out of another command's output; the paths and names read out of the failed command's own output, ssh's known_hosts line and a branch from origin/HEAD among them; the URL handed to open; and the sudo rule, which re-quoted your whole script and gave it to sh -c as root. All four are quoted now, and tests/test_injection.py runs each suggestion through a real shell and checks what the program actually received.  #1531 #1606

It asks before running your command again. To correct a command you have to know what it printed, and a shell keeps no record, so the command is run a second time — deploy, git push, rm, whatever it was, before you have agreed to anything. It asks first now, except where there is nothing to run or the program only ever reads.  #1126

The alias breaking because of something you pasted. The shell handed us your recent history in an environment variable, and the kernel will not pass a program a variable larger than 128K. One pasted command that size and the alias failed with "Argument list too long" — for that correction and for every one afterwards, until the entry fell out of the history window. It asks the shell for a smaller window instead.  #798

Rules that had quietly stopped matching. A rule that looks for a string in a tool's output stops working the day that tool rewords it, silently, and nothing in a test suite of fixtures notices. These were found by mistyping commands at the installed binaries and reading what came back: npm 7+, cargo 1.73+, docker 25+, git (main rather than master, and repository ownership), brew 4 (five of its seven rules), gem 3.2+, az, gradle 8 and terraform 1.x.  #1320 #1172 #1341 #1313 #1376

Crashes, and the places it did not work at all. An unreadable process tree, a process that exits while being killed, no terminal attached, a closed pipe, set -u, an empty alias, Fish's history moving to the XDG data directory, a command on Windows whose file is not spelled the way you typed it, and your environment being printed into debug output.  #1600 #1509 #1026 #1040 #1562 #1539 #1355 #1551 #1258 #1209 #1296 #995 #1506

And the test suite itself. Three of the thirty are about the tests rather than the tool: mock became unittest.mock, a memoized helper leaked between test cases, and usefixtures was applied to a fixture, where it does nothing.  #1344 #1523 #1550

And it is quicker, which has a section of its own.

Back to Contents

Supported everything

Python 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
Systems Linux, macOS, Windows — every Python on every one of them, on every push
Shells Bash, Zsh, Fish, Nushell, tcsh, PowerShell
Rules 174 of them, for git, docker, npm, yarn, pip, apt, dnf, zypper, pacman, brew, cargo, go, gradle, maven, terraform, aws, az, systemctl and the rest

Bash, Zsh, Fish, Nushell and tcsh are exercised end to end, in containers, driving a real terminal: the tests type a wrong command into the shell, type the alias, and check what the shell then runs. PowerShell gets the same treatment on Windows in CI, in Windows PowerShell 5.1 as well as 7, because the two do not agree about command chaining. The Python suite covers all six.

Back to Contents

Installation

The one-liner picks up whichever of uv, pipx or pip you already have, never asks for sudo, and never edits a file of yours:

curl -fsSL https://raw.githubusercontent.com/stamparm/thebleep/master/install.sh | sh

Read it first if you like — that is the same file as install.sh in this repository, and sh install.sh --dry-run prints what it would run without running it.

Or do it by hand, in whichever way you install command line tools:

uv tool install thebleep      # https://docs.astral.sh/uv/
pipx install thebleep         # https://pipx.pypa.io/
pip install --user thebleep   # if your distribution lets pip write there

The first two put The Bleep in an environment of its own, which is what you want for a tool rather than a library: nothing you pip install later can break it. On Debian, Ubuntu and Fedora, pip install --user is refused outright (PEP 668) — use uv or pipx there.

The alias, and why it costs nothing

Append the loader to your .bashrc, .zshrc or other startup script, once:

thebleep --alias-loader >> ~/.bashrc        # or ~/.zshrc, etc.

That writes a few lines of shell that define the alias the first time you use it, and nothing before — so opening a shell costs nothing at all. It is static: it does not need regenerating when The Bleep is upgraded, because all it does is call thebleep --alias on first use.

bleep() {
    eval "$(TB_SHELL=bash thebleep --alias bleep)";
    bleep "$@";
}

Any name you like, including the one your fingers already know:

thebleep --alias-loader BLEEP >> ~/.bashrc   # for Mondays
thebleep --alias-loader fuck >> ~/.bashrc

Paying at startup instead

eval $(thebleep --alias) in your startup file does the same job by starting a Python interpreter every time you open a shell, which is the 29 ms in the table above. Use it if you prefer it, and for the experimental instant mode, which has to set your prompt up front.

Your shell

--alias-loader writes the right thing for the shell you run it from, so the only difference between shells is the file it goes in:

Shell
Bash thebleep --alias-loader >> ~/.bashrc
Zsh thebleep --alias-loader >> ~/.zshrc
Fish thebleep --alias-loader >> ~/.config/fish/config.fish
tcsh thebleep --alias-loader >> ~/.cshrc
Nushell thebleep --alias-loader >> ~/.config/nushell/config.nu
PowerShell thebleep --alias-loader >> $profile

The few things worth knowing per shell:

  • Bash. A login shell reads ~/.bash_profile and not ~/.bashrc, which is how macOS's Terminal starts one. If the alias is not there in a new window, that is why; thebleep --alias-loader >> ~/.bash_profile as well, or source one from the other.
  • Zsh. ~/.zshrc, and that is all. If you use a framework that rewrites it, put the line in ~/.zshrc.local or wherever it tells you to.
  • Fish. ~/.config/fish/config.fish. Fish is asked for your aliases and functions by running fish -ic, so an alias defined only for interactive use is still found; the answer is cached against config.fish, so it is looked up again when you change it.
  • tcsh. ~/.tcshrc if you have one, ~/.cshrc otherwise.
  • Nushell. $XDG_CONFIG_HOME/nushell/config.nu if that is set — on every platform, which is the order Nushell itself reads them in — otherwise ~/.config/nushell/config.nu, %APPDATA%\nushell on Windows or ~/Library/Application Support/nushell on macOS. thebleep --doctor tells you which one it found. Here --alias-loader writes the alias itself rather than a stub that fetches it, because Nushell has no eval to define a command from a string — which costs nothing, since what your shell then reads at startup is a dozen lines of Nushell rather than a Python interpreter. Nushell 0.87 or newer, for commandline edit. What a correction does there.
  • PowerShell. $profile may not exist yet: New-Item -Force -Path $profile first. If PowerShell refuses to run the profile, that is the execution policy rather than us: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. Both Windows PowerShell 5.1 and PowerShell 7 work; a chained correction is written as first; if ($?) { second }, because && needs 7.
  • Anything else gets a generic alias that reads your last command with fc -ln -1. Rules that need to know which shell you are in will not; set TB_SHELL yourself if it guesses wrong.

Without an alias to tell it, The Bleep works out which shell it is in by walking up the process tree — which is right almost always, and wrong in the places where the process above it is not the shell: a container, an IDE's integrated terminal, a wrapper script, distrobox. --shell says so outright:

thebleep --shell fish --alias-loader >> ~/.config/fish/config.fish
thebleep --shell bash git brnch          # correct as though bash had asked

It takes any of bash, csh, fish, nu, powershell, pwsh, tcsh, zsh, and an unknown name is an error rather than a silent fallback. Naming the shell also skips the walk up the process tree, so it is the cheaper way round as well as the certain one.

Changes are only available in a new shell session. To make changes immediately available, run source ~/.bashrc (or your shell config file like .zshrc).

To run fixed commands without confirmation, use --yes (or -y for short):

bleep --yes

To fix commands recursively until succeeding, use the -r option:

bleep -r
Back to Contents

Updating

However you installed it:

uv tool upgrade thebleep
pipx upgrade thebleep
pip install --user --upgrade thebleep

Or run the one-liner again, which upgrades in place. The alias line in your startup file never needs regenerating — all it does is call thebleep --alias the first time you use it.

Uninstall

Reverse the two steps: delete the thebleep line from your shell's startup file, then remove the package with uv tool uninstall thebleep, pipx uninstall thebleep or pip uninstall thebleep.

How it works

The Bleep attempts to match the previous command with a rule. If a match is found, a new command is created using the matched rule and executed.

Commands with something in front of them

The interesting command is not always the first word:

$ sudo -u www-data git chekout main
$ bleep
sudo -u www-data git checkout main

sudo, doas, env FOO=bar, command, builtin, nice, nohup, setsid and stdbuf are peeled off — nested, in any combination — the command underneath is corrected by every rule as though you had typed it on its own, and the wrapper comes back in front of the suggestion exactly as you wrote it. That is one model applied to every rule, in place of the sudo-only decorator that 26 of them had to ask for individually — which is still there, and still works, for rules outside this repository.

It fails towards leaving your command alone. A wrapper that is not transparent is not peeled: sudo -i and sudo -s run a shell, sudo -e opens an editor, sudo -l lists privileges and command -v prints a path, so none of those is the command underneath in a hat. Neither is an option it does not recognise — that option might take a value, and mistaking a value for the command is worse than not offering a correction. Nor is a script with shell syntax in it, where the first word is not the only command anyway, nor a wrapper whose words would have to be re-quoted to be handed back. time, strace and valgrind are transparent and still not peeled, because the output being corrected from is partly theirs: time git stauts prints git's error and time's report, and a rule that picks a name out of a command's output would offer one of time's lines as a branch to check out.

The following rules are enabled by default:

  • adb_unknown_command — fixes misspelled commands like adb logcta;
  • ag_literal — adds -Q to ag when suggested;
  • aws_cli — fixes misspelled commands like aws dynamdb scan;
  • az_cli — fixes misspelled commands like az providers;
  • cargo — runs cargo build instead of cargo;
  • cargo_no_command — fixes wrong commands like cargo buid;
  • cat_dir — replaces cat with ls when you try to cat a directory;
  • cd_correction — spellchecks and corrects failed cd commands;
  • cd_cs — changes cs to cd;
  • cd_mkdir — creates directories before cd'ing into them;
  • cd_parent — changes cd.. to cd ..;
  • chmod_x — adds execution bit;
  • choco_install — appends common suffixes for chocolatey packages;
  • composer_not_command — fixes composer command name;
  • conda_mistype — fixes conda commands;
  • cp_create_destination — creates a new directory when you attempt to cp or mv to a non-existent one
  • cp_omitting_directory — adds -a when you cp directory;
  • cpp11 — adds missing -std=c++11 to g++ or clang++;
  • dirty_untar — suggests re-extracting a tar x that unpacked into the current directory into a directory of its own (it does not delete what was already unpacked — nothing in the archive says which of those files you already had);
  • dirty_unzip — the same for unzip;
  • django_south_ghost — adds --delete-ghost-migrations to failed because ghosts django south migration;
  • django_south_merge — adds --merge to inconsistent django south migration;
  • docker_daemon_not_running — starts Docker with systemctl when its daemon is not listening;
  • docker_login — executes a docker login and repeats the previous command;
  • docker_not_command — fixes wrong docker commands like docker tags;
  • docker_image_being_used_by_container — removes the container that is using the image before removing the image;
  • dry — fixes repetitions like git git push;
  • fab_command_not_found — fixes misspelled fabric commands;
  • fix_alt_space — replaces Alt+Space with Space character;
  • fix_file — opens a file with an error in your $EDITOR;
  • gem_unknown_command — fixes wrong gem commands;
  • git_add — fixes "pathspec 'foo' did not match any file(s) known to git.";
  • git_add_force — adds --force to git add <pathspec>... when paths are .gitignore'd;
  • git_bisect_usage — fixes git bisect strt, git bisect goood, git bisect rset, etc. when bisecting;
  • git_branch_delete — changes git branch -d to git branch -D;
  • git_branch_delete_checked_out — when you try to delete the branch you are on, checks out the default branch first and then deletes it: whatever origin/HEAD points at, or main or master if there is no remote to ask;
  • git_branch_exists — offers git branch -d foo, git branch -D foo or git checkout foo when creating a branch that already exists;
  • git_branch_list — catches git branch list in place of git branch and removes created branch;
  • git_branch_0flag — fixes commands such as git branch 0v and git branch 0r removing the created branch;
  • git_checkout — fixes branch name or creates new branch;
  • git_clone_git_clone — replaces git clone git clone ... with git clone ...
  • git_clone_missing — adds git clone to URLs that appear to link to a git repository.
  • git_commit_add — offers git commit -a ... or git commit -p ... after previous commit if it failed because nothing was staged;
  • git_commit_amend — offers git commit --amend after previous commit;
  • git_commit_reset — offers git reset HEAD~ after previous commit;
  • git_diff_no_index — adds --no-index to previous git diff on untracked files;
  • git_diff_staged — adds --staged to previous git diff with unexpected output;
  • git_dubious_ownership — adds the repository to safe.directory when git refuses to touch it because somebody else owns it;
  • git_fix_stash — fixes git stash commands (misspelled subcommand and missing save);
  • git_flag_after_filename — fixes fatal: bad flag '...' after filename
  • git_help_aliased — fixes git help <alias> commands replacing with the aliased command;
  • git_hook_bypass — adds --no-verify flag previous to git am, git commit, or git push command;
  • git_lfs_mistype — fixes mistyped git lfs <command> commands;
  • git_main_master — fixes incorrect branch name between main and master
  • git_merge — adds remote to branch names;
  • git_merge_unrelated — adds --allow-unrelated-histories when required
  • git_not_command — fixes wrong git commands like git brnch;
  • git_pull — sets upstream before executing previous git pull;
  • git_pull_clone — clones instead of pulling when the repo does not exist;
  • git_pull_uncommitted_changes — stashes changes before pulling and pops them afterwards;
  • git_push — adds --set-upstream origin $branch to previous failed git push;
  • git_push_different_branch_names — fixes pushes when local branch name does not match remote branch name;
  • git_push_pull — runs git pull when push was rejected;
  • git_push_without_commits — creates an initial commit if you forget and only git add ., when setting up a new project;
  • git_rebase_no_changes — runs git rebase --skip instead of git rebase --continue when there are no changes;
  • git_remote_delete — replaces git remote delete remote_name with git remote remove remote_name;
  • git_rm_local_modifications — adds -f or --cached when you try to rm a locally modified file;
  • git_rm_recursive — adds -r when you try to rm a directory;
  • git_rm_staged — adds -f or --cached when you try to rm a file with staged changes
  • git_rebase_merge_dir — offers git rebase (--continue | --abort | --skip) or removing the .git/rebase-merge dir when a rebase is in progress;
  • git_remote_seturl_add — runs git remote add when git remote set_url on nonexistent remote;
  • git_stash — stashes your local modifications before rebasing or switching branch;
  • git_stash_pop — adds your local modifications before popping stash, then resets;
  • git_tag_force — adds --force to git tag <tagname> when the tag already exists;
  • git_two_dashes — adds a missing dash to commands like git commit -amend or git rebase -continue;
  • go_run — appends .go extension when compiling/running Go programs;
  • go_unknown_command — fixes wrong go commands, for example go bulid;
  • gradle_no_task — fixes not found or ambiguous gradle task;
  • gradle_wrapper — replaces gradle with ./gradlew;
  • grep_arguments_order — fixes grep arguments order for situations like grep -lir . test;
  • grep_recursive — adds -r when you try to grep directory;
  • grunt_task_not_found — fixes misspelled grunt commands;
  • gulp_not_task — fixes misspelled gulp tasks;
  • has_exists_script — prepends ./ when script/binary exists;
  • heroku_multiple_apps — adds --app <app> to heroku commands like heroku pg;
  • heroku_not_command — fixes wrong heroku commands like heroku log;
  • history — tries to replace command with the most similar command from history;
  • hostscli — tries to fix hostscli usage;
  • ifconfig_device_not_found — fixes wrong device names like wlan0 to wlp2s0;
  • java — removes .java extension when running Java programs;
  • javac — appends missing .java when compiling Java files;
  • lein_not_task — fixes wrong lein tasks like lein rpl;
  • long_form_help — changes -h to --help when the short form version is not supported
  • ln_no_hard_link — catches hard link creation on directories, suggest symbolic link;
  • ln_s_order — fixes ln -s arguments order;
  • ls_all — adds -A to ls when output is empty;
  • ls_lah — adds -lah to ls;
  • man — changes manual section;
  • man_no_space — fixes man commands without spaces, for example mandiff;
  • mercurial — fixes wrong hg commands;
  • missing_space_before_subcommand — fixes command with missing space like npminstall;
  • mkdir_p — adds -p when you try to create a directory without a parent;
  • mvn_no_command — adds clean package to mvn;
  • mvn_unknown_lifecycle_phase — fixes misspelled life cycle phases with mvn;
  • npm_missing_script — fixes npm custom script name in npm run-script <script>;
  • npm_run_script — adds missing run-script for custom npm scripts;
  • npm_wrong_command — fixes wrong npm commands like npm urgrade;
  • no_command — fixes wrong console commands, for example vom/vim;
  • no_such_file — creates missing directories with mv and cp commands;
  • omnienv_no_such_command — fixes wrong commands for goenv, nodenv, pyenv and rbenv (eg.: pyenv isntall or goenv list);
  • open — either prepends http:// to address passed to open or creates a new file or directory and passes it to open;
  • pip_install — adds --user when pip install failed for want of permission. It does not offer sudo pip install; where --user is not enough, pip_externally_managed below has the answer;
  • pip_externally_managed — offers pipx or a virtual environment when pip refuses to install into the system Python (PEP 668);
  • pip_unknown_command — fixes wrong pip commands, for example pip instatl/pip install;
  • php_s — replaces -s by -S when trying to run a local php server;
  • ping_url — pings the host in a URL you pasted, not the URL;
  • port_already_in_use — kills process that bound port;
  • prove_recursively — adds -r when called with directory;
  • python_command — prepends python when you try to run non-executable/without ./ python script;
  • python_execute — appends missing .py when executing Python files;
  • quotation_marks — fixes uneven usage of ' and " when containing args';
  • path_from_history — replaces not found path with a similar absolute path from history;
  • rails_migrations_pending — runs pending migrations;
  • react_native_command_unrecognized — fixes unrecognized react-native commands;
  • remove_shell_prompt_literal — removes leading shell prompt symbol $, common when copying commands from documentations;
  • remove_trailing_cedilla — removes trailing cedillas ç, a common typo for European keyboard layouts;
  • rm_dir — adds -r when you try to remove a directory;
  • scm_correction — corrects wrong scm like hg log to git log;
  • sed_unterminated_s — adds missing '/' to sed's s commands;
  • sl_ls — changes sl to ls;
  • ssh_known_hosts — on a host key warning, suggests the ssh-keygen -R that ssh itself recommends, in front of your command, so you can see which key it drops before agreeing;
  • sudo — prepends sudo to the previous command if it failed because of permissions;
  • sudo_command_from_user_path — runs commands from users $PATH with sudo;
  • switch_lang — switches command from your local layout to en;
  • systemctl — correctly orders parameters of confusing systemctl;
  • terraform_init — runs terraform init before plan or apply;
  • terraform_no_command — fixes unrecognized terraform commands;
  • test.py — runs pytest instead of test.py;
  • touch — creates missing directories before "touching";
  • tsuru_login — runs tsuru login if not authenticated or session expired;
  • tsuru_not_command — fixes wrong tsuru commands like tsuru shell;
  • tmux — fixes tmux commands;
  • unknown_command — fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on hdfs dfs ls;
  • unsudo — removes sudo from previous command if a process refuses to run on superuser privilege.
  • vagrant_up — starts up the vagrant instance;
  • whois — fixes whois command;
  • workon_doesnt_exists — fixes virtualenvwrapper env name os suggests to create new.
  • wrong_hyphen_before_subcommand — removes an improperly placed hyphen (apt-install -> apt install, git-log -> git log, etc.)
  • yarn_alias — fixes aliased yarn commands like yarn ls;
  • yarn_command_not_found — fixes misspelled yarn commands;
  • yarn_command_replaced — fixes replaced yarn commands;
  • yarn_help — makes it easier to open yarn documentation;
Back to Contents

The following rules are enabled by default on specific platforms only:

  • apt_get — installs app from apt if it not installed (requires python-commandnotfound / python3-commandnotfound);
  • apt_get_search — changes trying to search using apt-get with searching using apt-cache;
  • apt_invalid_operation — fixes invalid apt and apt-get calls, like apt-get isntall vim;
  • apt_list_upgradable — helps you run apt list --upgradable after apt update;
  • apt_upgrade — helps you run apt upgrade after apt list --upgradable;
  • brew_cask_dependency — installs cask dependencies;
  • brew_install — fixes formula name for brew install;
  • brew_reinstall — turns brew install <formula> into brew reinstall <formula>;
  • brew_link — adds --overwrite --dry-run if linking fails;
  • brew_uninstall — adds --force to brew uninstall if multiple versions were installed;
  • brew_unknown_command — fixes wrong brew commands, for example brew docto/brew doctor;
  • brew_update_formula — turns brew update <formula> into brew upgrade <formula>;
  • dnf_no_such_command — fixes mistyped DNF commands;
  • nixos_cmd_not_found — installs apps on NixOS;
  • pacman — installs app with pacman if it is not installed (uses paru, yay, pikaur or yaourt if available, in that order);
  • pacman_invalid_option — replaces lowercase pacman options with uppercase.
  • pacman_not_found — fixes package name with pacman, paru, yay, pikaur or yaourt.
  • yum_invalid_operation — fixes invalid yum calls, like yum isntall vim;
  • zypper_no_such_command — fixes mistyped zypper commands and their abbreviations on openSUSE and SLE, like zypper isntall vim or zypper dpu.

The following commands are bundled with The Bleep, but are not enabled by default:

  • git_push_force — adds --force-with-lease to a git push (may conflict with git_push_pull);
  • python_module_error — installs the package a missing import needs. An import name is not a distribution name (import yaml wants PyYAML, cv2 wants opencv-python), and a mistyped import makes the suggestion pip install <typo>, so this is not on by default;
  • rm_root — adds --no-preserve-root to rm -rf / command.
Back to Contents

Creating your own rules

To add your own rule, create a file named your-rule-name.py in ~/.config/thebleep/rules. The rule file must contain two functions:

match(command: Command) -> bool
get_new_command(command: Command) -> str | list[str]

Rules can also contain the optional variables enabled_by_default, requires_output and priority.

Command has three attributes: script, output and script_parts. Your rule should not change Command.

Rules api changed in 3.0: To access a rule's settings, import it with from thebleep.conf import settings

settings is a special object assembled from ~/.config/thebleep/settings.py, and values from env (see more below).

A whole rule, for a kubectl that wants --namespace and did not get one:

import re

# Read out of the rule by the loader, so a `kubectl` rule is never even
# compiled for your `git push`. Worth declaring; see How it works.
from thebleep.utils import for_app


@for_app('kubectl')
def match(command):
    return 'the server doesn\'t have a resource type' in command.output


def get_new_command(command):
    return re.sub(r'^kubectl ', 'kubectl --namespace default ', command.script)


# All optional, and these are the defaults.
enabled_by_default = True
requires_output = True    # do not even try me without the command's output
priority = 1000           # lower is matched first

That is the whole interface. A rule reads a command and returns a string — or a list of strings, to offer several — and the one you accept is the one that runs.

side_effect, and why to think twice

A rule may also define:

side_effect(old_command: Command, fixed_command: str) -> None

which runs after you accept the correction, and only then — pressing tab to edit does not fire it, because nothing has run. It is supported and is not going away, and third-party rules that use it keep working.

It is still the wrong tool nine times out of ten. Whatever it does happens outside the command you were shown and agreed to, so the thing you approved is not the thing that happened — which is exactly how dirty_untar came to delete files and ssh_known_hosts came to drop a host key behind a warning you never read. Both are now rules that say what they do in the command itself, and both are better rules for it. Prefer shell.and_('the thing you want first', command.script): it is visible, it is refusable, and it appears in your history like anything else you ran.

More examples of rules, utility functions for rules, app/os-specific helpers.

Back to Contents

Settings

Several The Bleep parameters can be changed in the file $XDG_CONFIG_HOME/thebleep/settings.py ($XDG_CONFIG_HOME defaults to ~/.config):

  • rules — list of enabled rules, by default thebleep.const.DEFAULT_RULES;
  • exclude_rules — list of disabled rules, by default [];
  • require_confirmation — requires confirmation before running new command, by default True; when there's no terminal attached (a pipe, a subprocess or CI) confirmation is impossible, so the suggestion is only printed and nothing is run — pass --yes to apply it;
  • confirm_replay — asks before running your previous command a second time to read what it printed, by default True; see Reading the previous command;
  • wait_command — the max amount of time in seconds for getting previous command output;
  • no_colors — disable colored output;
  • priority — dict with rules priorities, rule with lower priority will be matched first;
  • debug — enables debug output, by default False;
  • history_limit — the numeric value of how many history commands will be scanned, like 2000;
  • alter_history — push fixed command to history, by default True;
  • wait_slow_command — max amount of time in seconds for getting previous command output if it in slow_commands list;
  • slow_commands — list of slow commands;
  • num_close_matches — the maximum number of close matches to suggest, by default 3;
  • excluded_search_path_prefixes — path prefixes to ignore when searching for commands, by default [];
  • instant_mode — read what scrolled past instead of running your command again, by default False; see Experimental instant mode;
  • repeat — if the corrected command fails too, correct that as well, by default False; --repeat does it for one run;
  • edit — hand the correction to your command line to edit instead of running it, by default False; --edit does it for one run, and tab does it for one suggestion; see Edit before you run;
  • explain — say which rule made each suggestion and what it matched, by default False; --explain does it for one run, and ? does it at the prompt; see Why am I being told this;
  • env — environment variables to set for your previous command when it is run again to read its output, by default {'LC_ALL': 'C', 'LANG': 'C'}, which is there so that rules can look for English error messages. Git also gets GIT_TRACE=1, so that git st can be resolved to whatever alias it stands for; nothing else does.

An example of settings.py:

rules = ['sudo', 'no_command']
exclude_rules = ['git_push']
require_confirmation = True
confirm_replay = True
wait_command = 10
no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
debug = False
history_limit = 9999
wait_slow_command = 20
slow_commands = ['react-native', 'gradle']
num_close_matches = 5
instant_mode = False
repeat = False
edit = False
explain = False
env = {'LC_ALL': 'C', 'LANG': 'C'}

Or via environment variables:

  • THEBLEEP_RULES — list of enabled rules, like DEFAULT_RULES:rm_root or sudo:no_command;
  • THEBLEEP_EXCLUDE_RULES — list of disabled rules, like git_pull:git_push;
  • THEBLEEP_REQUIRE_CONFIRMATION — require confirmation before running new command, true/false;
  • THEBLEEP_CONFIRM_REPLAY — ask before running your previous command again to read its output, true/false;
  • THEBLEEP_WAIT_COMMAND — the max amount of time in seconds for getting previous command output;
  • THEBLEEP_NO_COLORS — disable colored output, true/false;
  • THEBLEEP_PRIORITY — priority of the rules, like no_command=9999:apt_get=100, rule with lower priority will be matched first;
  • THEBLEEP_DEBUG — enables debug output, true/false;
  • THEBLEEP_HISTORY_LIMIT — how many history commands will be scanned, like 2000;
  • THEBLEEP_ALTER_HISTORY — push fixed command to history true/false;
  • THEBLEEP_WAIT_SLOW_COMMAND — the max amount of time in seconds for getting previous command output if it in slow_commands list;
  • THEBLEEP_SLOW_COMMANDS — list of slow commands, like lein:gradle;
  • THEBLEEP_NUM_CLOSE_MATCHES — the maximum number of close matches to suggest, like 5.
  • THEBLEEP_REPEAT — if the corrected command fails too, correct that as well, true/false.
  • THEBLEEP_EDIT — hand the correction to your command line to edit instead of running it, true/false.
  • THEBLEEP_EXPLAIN — say which rule made each suggestion and what it matched, true/false.
  • THEBLEEP_INSTANT_MODE — read what scrolled past instead of running your command again, true/false; see Experimental instant mode.
  • THEBLEEP_EXCLUDED_SEARCH_PATH_PREFIXES — path prefixes to ignore when searching for commands, by default [].

For example:

export THEBLEEP_RULES='sudo:no_command'
export THEBLEEP_EXCLUDE_RULES='git_pull:git_push'
export THEBLEEP_REQUIRE_CONFIRMATION='true'
export THEBLEEP_WAIT_COMMAND=10
export THEBLEEP_NO_COLORS='false'
export THEBLEEP_PRIORITY='no_command=9999:apt_get=100'
export THEBLEEP_HISTORY_LIMIT='2000'
export THEBLEEP_NUM_CLOSE_MATCHES='5'
Back to Contents

Third-party packages with rules

If you'd like to make a specific set of non-public rules, but would still like to share them with others, create a package named thebleep_contrib_* with the following structure:

thebleep_contrib_foo
  thebleep_contrib_foo
    rules
      __init__.py
      *third-party rules*
    __init__.py
    *third-party-utils*
  setup.py

The Bleep will find rules located in the rules module.

Back to Contents

Experimental instant mode

Correcting a command means knowing what it printed, which normally means running it again — the reason The Bleep asks first. Instant mode takes the other way out: it records your session with script as it happens and reads the log, so the previous command never runs twice and the question never comes up. It is the better answer where it works, and it is also the faster one.

Currently, instant mode only supports bash and zsh. zsh's autocorrect function also needs to be disabled in order for thebleep to work properly.

To enable instant mode, add --enable-experimental-instant-mode to the alias initialization in .bashrc, .bash_profile or .zshrc.

For example:

eval $(thebleep --alias --enable-experimental-instant-mode)

What it does, and where it stops

It is called experimental because it is, and it is worth being specific about which parts. This is what a real terminal was driven through, on bash 5.2 and zsh 5.9:

A correction with no rerun and no question works
After ctrl+c, or a window resize works
Unicode in the output works
Megabytes of output, wrapping the recording several times works
Megabytes of Unicode output, wrapping mid-character works
Output that was never text at all (a cat of a binary) works
After a full-screen program (less, vim, top) does not correct
After a shell started inside the shell does not correct

The two Unicode rows are one row in the tests and were two bugs until this release: the recording is a ring, so reading the last megabyte of it begins at whatever byte is a megabyte back, and that is inside a character as often as the output has multibyte characters in it. Decoding it raised, and the traceback came out of the middle of a correction. tests/output_readers/test_read_log.py holds every offset into that seam.

The last two rows are the same limitation. What is recorded is the raw terminal stream, and where one command's output ends is worked out by looking for a mark that instant mode puts in your PS1. A program that takes over the screen moves the cursor wherever it likes and the marks stop lining up with what is on it; a nested shell writes a second set of them. For the same reason it needs your PS1 to still contain that mark, so a prompt framework that rebuilds PS1 after the alias is set up — powerlevel10k, starship, some oh-my-zsh themes — switches instant mode off, with a warning saying so.

Where it does not work it does not go wrong: the mark is missing, or the command has scrolled out of the recording, and instant mode is simply not in play for that correction — which takes the ordinary route and asks before it runs anything again. Falling back gains nothing: the question is the same question, and the same short list of programs that only ever read is what skips it.

What is fixed rather than documented:

  • The recording is yours alone. It used to be created world-readable in /tmp — a megabyte of everything that had scrolled past, which is the contents of every file you read, every token a command printed, and every password typed at a prompt that echoes. It is mode 0600 now, in $XDG_RUNTIME_DIR where there is one, created with O_EXCL and O_NOFOLLOW so a name somebody else got to first is refused rather than opened.
  • It goes when the session goes. Closing the terminal used to leave the recording, the logger and the shell inside it running for the rest of the login session. The logger removes its own recording on the way out however it leaves, and the shell that started it has a trap as a backstop for SIGKILL.
  • No more holes in it. When the recording filled up, the chunk that overflowed was dropped rather than written after the room was made, so a busy session lost up to a kilobyte of output every time it wrapped.
  • The terminal is put back. A shell that exited normally used to leave your terminal in raw mode.
Back to Contents

Performance

The numbers are at the top, and they are meant to be checked rather than believed. Same machine, same Python, 30 runs each, medians, measured with the harness in bench/; the run they come from is committed as bench/results/final.json, and the chart at the top is written from that file by bench/chart.py, so the two cannot drift apart.

The shell startup row is the eager alias, eval "$(thebleep --alias)", which starts an interpreter every time you open a shell. The loader is the row that is not in the table, because there is nothing to time: it is five lines of shell that define a function, and the interpreter starts the first time you use the alias instead. Timing it against a shell with nothing configured at all comes out inside the run-to-run spread of shell startup, which is the honest answer rather than a number.

Reproduce it yourself:

./bench/setup_subjects.sh python3.11      # builds both, from their own packages
BENCH_CPU=2,3 ./bench/bench.py --runs 30 \
    --subject fuck=bench/.venvs/fuck-3.11/bin/thefuck \
    --subject bleep=bench/.venvs/bleep-3.11/bin/thebleep

Python 3.11 is used for the comparison because The Fuck cannot start on 3.12 or newer — it imports distutils, which is no longer in the standard library. On this machine the interpreter itself costs 9 ms before either app runs a line, so that is the floor both are measured against. The environment block in the result file records the commit it was measured at, the kernel, the CPU and the harness's interpreter, so git show is what says which source those numbers belong to.

Where the time went:

  • Rules are compiled once, not on every command. The compiled rules live in a cache keyed by the interpreter and the rule files' timestamps.
  • Most rules are never loaded. A rule that declares @for_app('git', ...), or whose match needs a particular string in the output, cannot match your brew install — and that is readable from the rule's syntax tree without running it. A typical command now reaches about a fifth of the 174 rules, and one for a tool with many rules of its own — git — under a quarter, instead of all of them. Rules that don't say what they are about are always loaded, so this makes corrections faster, never fewer. tests/test_performance.py fails if dispatch goes broad again.
  • Startup imports almost nothing, and so does a correction. pyte, psutil, argparse, pprint and the five shells you are not using never arrive at all; ast, pickle, socket, uuid, tempfile, shutil, subprocess, difflib and ctypes arrive only on the paths that use them — roughly half of what a correction used to open. tests/test_performance.py names every module that has to stay out and holds the total to a budget, measured on whatever machine it is running on: the absolute count depends on the interpreter and on how the package was installed, so it is a test rather than a number here. This matters most on Windows, where every module is a file a virus scanner reads before the interpreter may map it.
  • The failed command's output is read while it runs. It used to be read after the command exited, which deadlocks as soon as the output fills the pipe buffer: anything printing more than about 64KB waited out the full timeout and then produced nothing to correct from. That is the 28.4× row above, and it is a correctness fix as much as a speed one.
  • Nothing is scanned twice. The list of everything on your $PATH is remembered until a directory on it changes.

If a cache ever gets in your way, thebleep --clear-cache removes them all, and THEBLEEP_NO_RULE_PACK=true turns the rule cache off entirely.

On Windows

The Fuck has been called slow on Windows for years, and the reason is not either tool's own logic. Windows charges for opening files, and a Python module is a file the interpreter has to find and then open — with a virus scanner reading it first. So the work was to open fewer of them, which is the module list above, and it is the change that matters most here.

There are no numbers in this section, and that is deliberate. The Linux table at the top comes from a committed result file with the machine, the kernel, the CPU, the interpreter and the source commit recorded in it, produced by a harness in this repository that anybody can run. Nothing equivalent exists for Windows: the GitHub runner is Windows Server rather than a desktop with Defender in its default configuration, and a figure measured once on somebody's laptop with no artifact behind it is marketing rather than evidence. If you would like the same table for Windows, bench/bench.py runs there — bench/README.md says how — and a recorded run would be a welcome pull request.

What is checked on Windows, on every push: the whole test suite on Python 3.9 through 3.14, and the correction loop end to end in real Windows PowerShell 5.1 and PowerShell 7, because the two do not agree about command chaining. The import budget in tests/test_performance.py is enforced there as well as everywhere else, which is what stops the thing that made it slow from coming back.

Two costs are left, and neither belongs to either tool: an interpreter takes several times longer to start on Windows than on Linux, and the failed command still has to be run a second time to see what it printed. Both tools pay both.

Back to Contents

Developing

See CONTRIBUTING.md

License MIT

Project License can be found here.

Back to Contents

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thebleep-4.0.2.tar.gz (270.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thebleep-4.0.2-py3-none-any.whl (231.3 kB view details)

Uploaded Python 3

File details

Details for the file thebleep-4.0.2.tar.gz.

File metadata

  • Download URL: thebleep-4.0.2.tar.gz
  • Upload date:
  • Size: 270.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for thebleep-4.0.2.tar.gz
Algorithm Hash digest
SHA256 83b8c34e5635a84a68b9385a34dc827436e9755c94ebd8cc8b7dd3ca604e03a9
MD5 5b2f7d1302ba7caafc35607bb4468754
BLAKE2b-256 5f2f09f60aac77693e03fedf145e14fcd768d9faf601a705f991132aae9f356a

See more details on using hashes here.

Provenance

The following attestation bundles were made for thebleep-4.0.2.tar.gz:

Publisher: release.yml on stamparm/thebleep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file thebleep-4.0.2-py3-none-any.whl.

File metadata

  • Download URL: thebleep-4.0.2-py3-none-any.whl
  • Upload date:
  • Size: 231.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for thebleep-4.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d001b43cf3507fd73d29e21a75b8cad30e48ad10c743df65d4134c54cf76ba4d
MD5 6216616997c4722603599fe7b4cea3cb
BLAKE2b-256 a6f1c052d4b695d29b841c9152a1a9628e9339a79762a0f8715376e5fde1d590

See more details on using hashes here.

Provenance

The following attestation bundles were made for thebleep-4.0.2-py3-none-any.whl:

Publisher: release.yml on stamparm/thebleep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page