Install
One file, and nothing else to install beside it. Three ways to get it, and the same binary at the end of all of them.
curl --proto '=https' --tlsv1.2 -LsSf https://usetu.la/install.sh | sh
- --proto '=https'
- Only HTTPS, on redirects too
- --tlsv1.2
- Nothing older than TLS 1.2
- -L
- Follow a redirect
- -s
- No progress bar
- -S
- But still show errors
- -f
- Stop on an HTTP error, so an error page is never piped into a shell
This pipes a script into your shell, so read it before you run it. install.sh is the file the command fetches, copied from the one in the repo — the same file, and the one every test runs against.
tula checks for a new release once a day and says so in a line. It never installs one without asking: /update shows what it would install and where to check it, and /update install is the step that goes ahead.
Where it puts things
Everything goes under ~/.tula. Each version gets its own folder, and ~/.tula/bin/tula is a link pointing at the one you run.
If ~/.tula/bin is not on your PATH, the installer adds a line to your zsh, bash or fish profile and tells you which file it changed. On any other shell it prints the line and changes nothing. Set TULA_NO_MODIFY_PATH=1 to make it print rather than edit, whatever your shell.
One exact version
Set TULA_VERSION to install a version instead of the newest. It also takes latest, which is what you get anyway. In CI, add TULA_REQUIRE_ATTESTATION=1 to stop the install when the build attestation cannot be checked, rather than warn about it.
Update
/update install from inside tula does this without leaving it. Running the command again does the same thing, and is the one that still works when tula will not start.
It downloads nothing when the newest release is the one you already have — it says so and relinks, which takes about a second. TULA_FORCE=1 fetches and checks that same version again anyway.
curl --proto '=https' --tlsv1.2 -LsSf https://usetu.la/install.sh | sh
Go back
Old versions stay on disk, so going back is a change of link rather than another download.
ln -sf ~/.tula/versions/<version>/tula ~/.tula/bin/tulaRemove
Delete the folder, and the line the installer added to your shell profile. Your keys are kept elsewhere, so this leaves them alone.
rm -rf ~/.tulabrew install hsnice16/tap/tula
That names the tap and the formula together, which is the only way to do it in one command. To type the short name instead, tap once with brew tap hsnice16/tap and brew install tula works on that machine from then on.
Homebrew downloads the archive from the same GitHub release the install script uses, and checks it against the checksum written into the formula. It keeps the binary in its own prefix, not under ~/.tula.
There are two names to install from. tula moves only when a release is promoted to it, so a build found to be wrong is skipped by not promoting the next one. tula-latest takes every release, pre-releases included.
tula checks for a new release once a day and says so in a line. It never installs one without asking, and on Homebrew it does not install one at all — that is brew upgrade tula, so brew is never left naming a version that is not running.
One exact version
Every release also gets a formula of its own, and the tap keeps them. A pinned formula is keg_only: installing it does not put it on your PATH, so linking it is the step that says which tula you mean.
brew install hsnice16/tap/tula@<version> brew link --overwrite --force tula@<version>
Update
brew upgrade tulaGo back
Install and link a pinned formula from above. A Homebrew formula holds one version, so those pinned names are what makes an older build reachable at all.
Remove
That leaves the tap behind, which is a few lines of text and nothing else. brew untap hsnice16/tap drops it too.
brew uninstall tulanpm install -g @hsnice16/tula
Checked a different way. npm repackages the release instead of serving it, so the GitHub attestation the other two channels carry does not cover this tarball. It is published with npm's own sigstore provenance instead — npm audit signatures checks it, and npmjs.com shows which workflow and commit built it. What npm's integrity hash proves is only that the file arrived intact, which is a different question again.
You need Node to install it. You do not need Node to run it: the package carries the same binary as the other channels and puts it in place of its own launcher. It does not install under ~/.tula.
tula checks for a new release once a day and says so in a line. It never installs one without asking, and on npm it does not install one at all — that is npm update, so npm is never left naming a version that is not running.
One exact version
npm keeps every version it has published, so naming one is all it takes. It is not a pin, though: npm update moves you off it to the newest release, however far away that is.
npm install -g @hsnice16/tula@<version>Update
Installing again without a version does the same thing.
npm update -g @hsnice16/tulaGo back
Name the older version, the same command as above. npm keeps what it has published, so an old build is a download rather than something you needed to have kept.
Remove
npm uninstall -g @hsnice16/tulaConfirm it worked
tula --versionIt prints the version. If your shell says it cannot find tula, it is on disk and your PATH does not reach it — which line fixes that is the one thing the three channels do not share.
- Install script
- Near the end it printed one of three.
added tooralready inmeans the PATH line is in a profile already, and this shell started before it — open a new one.add it yourselfmeans it changed nothing, and theexportline beside it is yours to add. - Homebrew
- A pinned
tula@<version>iskeg_only, so it is installed and deliberately not on your PATH until youbrew linkit. - npm
- Its launcher goes to npm’s global bin directory, which has to be on your PATH.
npm prefix -gnames the folder thatbinsits in.
Every install is checked
Published checksum
Always. The install stops if the download does not match it.
Build attestation
GitHub signs it as the release is built. Checking it needs the GitHub CLI, which most people do not have — read the section below before you count on it.
Versioned installs
Every release keeps a name of its own, so an older build is still there after a newer one arrives.
What it runs on
| macOS | Yes | Intel and ARM, 64-bit. |
| Linux | Yes | Intel and ARM, 64-bit. Needs glibc. |
| Alpine, or any musl Linux | No | The installer says so and stops. |
| Windows | Through WSL | Install inside WSL, where it is Linux. There is no native build. |
Proving who built it
A checksum proves the file arrived whole. It does not prove who made it: the checksum is published beside the file, so whoever could swap one could swap the other. The build attestation is the part that proves origin — GitHub signs it as the release is built, and nobody who gets hold of the files afterwards can reissue it.
Checking one needs the GitHub CLI, signed in with gh auth login. It will not fetch an attestation without a token, even for a public repository.
Most people do not have the GitHub CLI, and nothing here requires it. Without it you still get an HTTPS-only download and a checksum the installer will not skip — but nothing has proved who built the binary, and the installer says so in as many words when it finishes.
curl --proto '=https' --tlsv1.2 -fLO https://github.com/hsnice16/tula/releases/download/v0.1.2/tula-v0.1.2-darwin-arm64.tar.gz gh attestation verify tula-v0.1.2-darwin-arm64.tar.gz --repo hsnice16/tula --signer-workflow hsnice16/tula/.github/workflows/release.yml
The download is the first step, not a repeat of one: the attestation covers the archive rather than the binary inside it, and the installer keeps no copy of the archive.
Your keys are kept apart
Whichever way you installed it, what you save lives in ~/.config/tula, away from the binary. Reinstalling does not touch it, updating does not touch it, and removing tula leaves it where it is.
So deleting it is a separate step, and a deliberate one. Run this when you are done with tula, not when you are reinstalling it.
rm -rf ~/.config/tula