Commands

From docs/COMMANDS.md at 4e43843 View on GitHub

Every ketch command in one place: what it does, the form it takes, and one working example. PKG below is an installed name, an alias, or owner/repo — most commands that take one also accept @version on the end (sharkdp/[email protected]). Global flags work everywhere: --root <DIR> points at a different ketch tree, -v/--verbose shows what ketch is doing, -q/--quiet prints only errors and requested data, --no-color disables colour, and --no-emoji drops the icons in front of status lines.

Icons. On a terminal, each status line starts with an icon for what it reports: 📦 install, ⏫ upgrade or update, 🧹 uninstall or remove, ⏬ download, 🔗 link, ⏪ rollback, 🔍 search, 🩺 doctor, and otherwise ✅ success, ❗ warning, ❌ error, 💡 note. They are on by default (emoji in config.toml, KETCH_EMOJI) and never appear in a pipe or a file, under TERM=dumb, in --json or --names-only output, in table data, or in the log.

Install and remove

ketch install [PKG]... [options]

Install one or more packages. Inference picks the release asset matching the host (architecture, OS, libc); a manifest or --asset says what to do instead.

ketch install BurntSushi/ripgrep  # any repo that publishes releases
ketch install rg                  # or a name the registry knows
ketch install sharkdp/[email protected]  # or an exact version
ketch install --path ./mytool     # a local binary, archive, symlink, or .app

A package that is already installed is not updated behind your back. With a newer release, install asks <pkg> <installed> is installed; update to <latest>? (default no) and, on yes, updates it the way ketch upgrade does; --yes answers yes, and without a terminal it stops with exit 5 and says to pass --yes or run ketch upgrade. With nothing newer it fails with exit 5: cannot install <pkg>: <version> is already installed and no update is available (--force reinstalls). An exact version ([email protected]) and a pinned package behave as before.

Options: --path <PATH> installs a local file (equivalent to local:<PATH>); --name <NAME> sets the installed name for a single package; --force/-f reinstalls the requested version even when present; --pre considers prereleases and keeps the package on them: later upgrade, outdated and list look for prereleases without --pre, and info shows channel: pre (an exact prerelease tag, [email protected], does the same; a plain install moves the package back to stable); --draft considers GitHub draft releases too, prereleases with them, and keeps the package on drafts (channel: draft); pkg@<tag> --draft finds a draft by the tag it will get. GitHub shows drafts only to a token with push access to the repository (KETCH_GITHUB_TOKEN, GITHUB_TOKEN, GH_TOKEN or github_token), so without one --draft stops with that reason; a draft's files download through the API with the token, and are checked like any other. Other sources have no drafts and refuse --draft; --no-link unpacks without linking onto PATH; --require-checksum refuses releases with no published checksum; --asset <NAME> takes one release file by exact name (asks for confirmation, since the platform check is skipped); -j/--jobs <N> and -y/--yes control parallelism and prompts. Aliased as ketch i.

A tag that is not a version (nightly, dev, canary, edge) is a floating tag: the project republishes it under the same name. pkg@nightly installs it on channel: dev, recording the build it got — the asset's digest and when it was published. Installing the same tag again is already installed until the tag names another build, and then it takes the new one.

When a release ships several binaries sharing the package's name and no manifest names one, only one of them is linked: the one --bin <NAME> names; failing that, the binary named exactly like the package; failing that, a choice remembered from last time (in state.json, or during ketch sync in ketch.lock), or a numbered pick in a terminal. Binaries with other names are linked as usual. With --yes or without a terminal there is no pick, and the install fails with the candidates listed — see the bin section of MANIFESTS.md.

--bin <NAME> (single package) answers that question up front, so it works without a terminal: ketch install owner/rtok --bin rtok-cli. It is stored in state.json like a pick and reused on later upgrades. It fails when no binary in the release has that name, and when the package's manifest already has a bin (change that instead).

ketch uninstall <NAME>...

Remove installed packages. Names resolve like install (installed name, binary, or owner/repo); a typo stops the command before anything is removed. The package's whole store folder goes, including anything an interrupted update left in it. A name that is not installed prints <name>: not found and exits 4; every missing name is listed and nothing is removed.

ketch uninstall rg
ketch uninstall fd ripgrep --yes  # skip the confirmation

Aliased as ketch remove and ketch rm.

ketch upgrade [NAME]...

Upgrade installed packages to their latest release. Empty means every unpinned package. Shows a from -> to table, asks, then installs.

Each version is unpacked into a fresh folder of its own, so nothing the old version shipped can linger in the new one, and a leftover from an interrupted upgrade is removed first — or the upgrade stops, naming it. The previous version's folder is kept beside it for ketch rollback until ketch prune.

ketch upgrade              # everything unpinned
ketch upgrade ripgrep fd   # just these
ketch upgrade --dry-run    # show the plan, change nothing

Options: --pre considers prereleases; --draft considers GitHub draft releases too (a package installed with --draft does without it). A draft that was published is an upgrade to the release under its tag; a draft that was deleted is a warning. A package on a floating tag (channel: dev) follows that tag: the tag naming another build is the upgrade, decided by the asset's digest, or by when the release was published when the source gives no digest; a newer version under another tag is not. --force upgrades pinned packages too; --bin <NAME> (exactly one package) picks which binary to link when the new release ships several sharing the package's name, as on install; -j/--jobs <N>, -y/--yes. Pinned and local: packages are skipped unless forced.

ketch rollback <PKG> [--to <VERSION>]

Switch a package back to a version still on disk. Without --to, restores the previous retained version.

ketch rollback ripgrep
ketch rollback rg --to 14.1.0

ketch prune [NAME]... [--keep <N>]

Remove retained old versions according to the retention policy. Empty means every installed package. --keep <N> updates the stored policy.

ketch prune
ketch prune ripgrep --keep 2

ketch pin <NAME>... / ketch unpin <NAME>...

Hold a package at its current version (pin), or release the hold (unpin). Pinned packages are skipped by upgrade unless --force is passed.

ketch pin ripgrep
ketch upgrade --force        # upgrades pinned packages too
ketch unpin ripgrep

Re-create (link) or remove (unlink) the bin-dir links for an installed package. unlink keeps the payload installed; upgrade preserves the linked state.

ketch unlink ripgrep   # keep it, take it off PATH
ketch link ripgrep     # put it back

ketch import winget|brew|linux <NAME> [--dry-run] [--yes]

Add a package another package manager already knows. ketch reads that manager's definition, converts it to a user manifest in ~/.ketch/manifests/<name>.toml, and installs it the normal way, with the asset and checksum the definition names.

ketch import brew codex                    # a Homebrew cask (or formula)
ketch import brew fly --cask               # only look for a cask; --formula for a formula
ketch import winget BurntSushi.ripgrep.MSVC  # a winget id, case-sensitive
ketch import linux lazygit                 # Arch Linux, then the AUR
ketch import linux obsidian --dry-run      # print the manifest, change nothing
SourceWhere the definition comes from
wingetthe installer manifest of the newest version in winget-pkgs
brewthe cask or formula JSON from formulae.brew.sh
linuxthe .SRCINFO of the Arch Linux package, or of the AUR's -bin/-appimage package when Arch builds it from source

Only a package whose downloads are GitHub release assets (https://github.com/<owner>/<repo>/releases/download/...) converts. A download from anywhere else — a vendor's CDN, SourceForge, a source tarball, a GitHub homepage with downloads hosted elsewhere, or a mix — writes nothing and exits 1 with <name> can't be converted: it is not distributed through GitHub Releases, and that is not supported yet. So do installers ketch cannot run (.msi, .msix, Inno or NSIS .exe, .deb, .rpm), cask artifacts beyond an app or binaries (pkg, installer), and two files for one platform.

The manifest holds only what ketch needs: name, source, kind for an app, bin, and one [asset.target] pattern per platform, with the version replaced by * so it keeps matching. It starts with a # Written by `ketch import …` line; a file of that name without it is yours, and import refuses to replace it.

Running it again is safe: when the converted manifest and the installed version are already what the source says, it prints Everything is up to date and does nothing. A new version upstream rewrites the file and upgrades; a changed manifest at the same version reinstalls.

The catalogue addresses can be pointed elsewhere (a mirror, or a test double) with KETCH_IMPORT_BREW, KETCH_IMPORT_WINGET_API, KETCH_IMPORT_WINGET_RAW, KETCH_IMPORT_ARCH, KETCH_IMPORT_ARCH_GITLAB and KETCH_IMPORT_AUR. The winget listing goes through the GitHub API, so it uses your GitHub token when one is set.

Inspect

ketch list

ketch list [local|remote] [--json] [--names-only] [--pre] [--draft], aliased as ketch ls.

Show what is installed, what the registry offers, or both in one table with the newest version of each.

CommandShowsNetwork
ketch list localInstalled packages, from the install recordNever
ketch list remotePackages the registry offers, with their latest versionNeeded; fails without it
ketch listBoth, one row per package, sorted by nameUsed for latest; without it, the local part

"The registry" here is the local copy ketch update fetches, plus your own manifests in ~/.ketch/manifests (yours win when a name is in both). The few packages compiled into ketch as a fallback are not listed; run ketch update to see the registry they come from.

Columns.

  • package — the name to install, upgrade or uninstall it by.
  • installed — the installed version, followed by the channel it follows when that is not stable: (pre) for prereleases (installed with --pre, or from a prerelease tag), (draft) for GitHub drafts (installed with --draft), (dev) for a floating tag such as nightly; a package on the default stable channel carries no mark. (pinned) means ketch pin holds it; (+N retained) means N earlier versions are kept for ketch rollback. Empty for a package that is not installed.
  • latest — the newest release of the package's source. (update available) follows it when that is an update for the installed version. ? means the source did not answer. Empty for a package installed from a local path, which has no upstream to ask.
  • source — where the package comes from. For an installed package, that is where it was installed from, and where its next version will come from.
  • description (remote only) — the registry's one-line summary, cut to the terminal width (COLUMNS sets another width; nothing is cut when the output is piped).

Markers. In ketch list, an installed package has * in the first column. On a colour terminal the marker is ●, the name is bold, and (update available) is yellow. CLICOLOR_FORCE=1 asks for colour even into a pipe; --no-color and NO_COLOR turn it off.

Update available is decided by the same rule ketch outdated uses, for every channel. latest is the newest release the package's source reports, from the same lookup ketch outdated makes: stable releases only, unless --pre is given, prerelease = true is set in config.toml or in the package's manifest, or the package is installed on the pre channel; drafts too with --draft or on the draft channel. That version is compared with the installed one, and only a strictly newer version counts; a release with the installed tag does not, with two exceptions. A package on the draft channel is offered the release its draft was published as, under the same tag, and a warning says so when that draft is gone. A package on the dev channel is looked up under its own tag, and latest shows that tag; a new build published under it is an update. A pinned package is never offered an update.

Pinned packages are listed with both versions and (pinned), get no (update available), and are left out of the footer. ketch unpin lets them move again.

Packages from outside the registry — installed from owner/repo or another scheme:id reference — are listed too, and their latest comes from their own source. One installed from a local path (ketch install --path) is listed with an empty latest.

Under the table, N updates available: ketch upgrade <names> names every package with an update, as the command that takes them all.

Offline, and packages that do not answer. Latest versions are looked up in parallel (jobs at a time, 4 by default), with a counter on stderr while they load. A source that fails — rate-limited, gone, unreachable — shows ? in latest, and one line under the table names each such package; every other row is still printed and the command still succeeds. When no source answers at all, the network is taken to be missing: ketch list prints the ketch list local table followed by latest: offline and exits 0, while ketch list remote, which has nothing to show without it, exits non-zero.

Answers are cached for 10 minutes in ~/.ketch/cache/latest.json, per source and per channel asked (stable, pre, draft, or a dev package's own tag), so listing twice in a row asks GitHub once. A cache file written by an older ketch is ignored and replaced. Only answers are cached, never failures. Delete the file to ask again sooner; ketch outdated and ketch upgrade never read it.

--json prints, for each mode:

  • local — an array of {"name", "installed", "channel", "pinned", "retained", "source"}, with retained the list of versions kept for rollback and channel the release channel the install follows (stable, pre, draft or dev).
  • remote — an array of {"name", "latest", "description", "source"}, with latest null for a source that did not answer.
  • no mode — {"packages": [...], "unreachable": [...]}. Each package is {"name", "installed", "channel", "latest", "update_available", "pinned", "source"}; installed and channel are null when it is not installed and latest is null when it is unknown. unreachable names the packages whose latest is unknown. Offline, packages holds only installed packages.

--pre looks for prereleases when filling the latest column, for every row, the way ketch outdated --pre does. It changes nothing in ketch list local, which asks no source.

--draft looks for GitHub draft releases too, and prereleases with them, in the latest column of installed packages, the way ketch outdated --draft does. Registry rows are left alone: drafts are visible only with a token that has push access to the repository. A package whose source has no drafts is asked without them, and one without such a token shows ?.

--names-only prints the names alone, one per line, for each mode; it never touches the network.

Changed in 0.7. ketch list used to show installed packages only, and its --json was an array of install records; it now shows everything, in the shape above. Scripts that want the installed packages should call ketch list local. ketch list --installed is a hidden alias of ketch list local, kept for one release.

$ ketch list local
package  installed        source
fd       v10.4.2          github:sharkdp/fd
ripgrep  14.1.1           github:BurntSushi/ripgrep
rtok     v0.9.0 (pinned)  github:pyrlyn/rtok
$ ketch list remote
package  latest   description
cox      v0.1.0   Modular terminal coding agent
ketch    v0.6.1   Catch releases straight from GitHub
ripgrep  15.2.0   Recursively search directories for a regex pattern
rtok     v0.10.0  Reduce the context AI coding agents must carry
runa     ?        Run AI models locally (GGUF via llama.cpp) or through the OpenAI and Anthropic APIs
swarfr   v0.1.0   Shrink Cargo target directories without slowing builds
? means the latest release could not be checked: runa
$ ketch list
   package  installed        latest                      source
   cox                       v0.1.0                      github:pyrlyn/cox
*  fd       v10.4.2          v10.5.0 (update available)  github:sharkdp/fd
   ketch                     v0.6.1                      github:pyrlyn/ketch
*  ripgrep  14.1.1           15.2.0 (update available)   github:BurntSushi/ripgrep
*  rtok     v0.9.0 (pinned)  v0.10.0                     github:pyrlyn/rtok
   runa                      ?                           github:pyrlyn/runa
   swarfr                    v0.1.0                      github:listepo/swarfr
2 updates available: ketch upgrade fd ripgrep
? means the latest release could not be checked: runa

Without a network:

$ ketch list
package  installed        source
fd       v10.4.2          github:sharkdp/fd
ripgrep  14.1.1           github:BurntSushi/ripgrep
rtok     v0.9.0 (pinned)  github:pyrlyn/rtok
latest: offline
$ ketch list remote
     error could not reach any package source to check the latest versions; `ketch list local` works offline
ketch list local --json      # installed packages, for scripts
ketch list --names-only      # every package name, one per line
ketch list remote --json     # the registry with latest versions

ketch outdated [--json] [--pre] [--draft]

Show installed packages that have a newer release. A source that cannot be reached is a warning, not a failure — unless nothing could be checked at all. --draft compares against GitHub draft releases too, as on upgrade. A package on a floating tag is outdated when its tag names another build.

ketch outdated
ketch outdated --json   # {"status","outdated","failed","unreachable"}

ketch info <PKG> [--assets] [--releases [--draft]] [--json]

Show details about a package, installed or not: source, description, latest and installed versions, links, retention, verification.

ketch info BurntSushi/ripgrep
ketch info rg --assets     # every release asset with its score and why
ketch info rg --releases   # the same table `ketch releases` prints, below

--releases adds the package's releases under the details, newest 20, in the format of ketch releases, and --draft lists drafts among them as ketch releases --draft does; with --json they are the releases array. A source that cannot be reached is a warning and the section is left out (null in JSON).

Aliased as ketch show.

ketch releases <PKG> [--pre] [--draft] [-n <LIMIT>] [--json]

List the releases a package's source has published, newest first as the source returns them, and mark the one that is installed. Needs the network.

$ ketch releases fd -n 3
   tag      version  published   kind
   v10.5.0  10.5.0   2026-08-26  stable
*  v10.4.2  10.4.2   2026-03-10  stable
   v10.4.1  10.4.1   2026-03-08  stable
  • tag and version — the release's tag and the version ketch reads from it and compares, without the tag's spelling (v10.4.2 reads 10.4.2). A tag that is not a version at all (nightly) has - for the version.
  • published — the day the source says the release was published, or -.
  • kind — stable; pre, for a release the forge flags as a prerelease or whose version has a prerelease part (2.0.0-rc.1); dev, for a tag that is not a version (a floating nightly, even when it is also flagged prerelease); draft, for an unpublished GitHub release, listed only with --draft or for a package installed on the draft channel.
  • A * in the first column (● on a colour terminal) marks the release the package is installed from, when it is among those listed.

Only stable releases are listed unless --pre is given, prerelease = true is set in config.toml or the package's manifest, or the package is installed on the pre or dev channel (GitHub flags a nightly as a prerelease); a repository that has never published a stable release lists its prereleases anyway. --draft lists GitHub drafts too, and prereleases with them; a package installed with --draft lists them without it. As with ketch install --draft, that needs a token with push access to the repository (KETCH_GITHUB_TOKEN, GITHUB_TOKEN, GH_TOKEN or github_token), and stops naming what is missing; other sources have no drafts and refuse it. -n (default 20, at least

  1. caps the list; a source may return fewer. An empty list prints no releases.

--json prints an array of {"tag", "version", "published_at", "kind", "installed"}; version and published_at are null when unknown.

ketch releases ripgrep
ketch releases sharkdp/fd --pre --json

ketch why <PKG> [--json]

Explain how a package would be resolved, without installing it: which manifest tier answered, which release and asset were picked, and why.

ketch why sharkdp/[email protected]
ketch why rg --json

ketch changelog <PKG> [--latest] [--file] [--release]

Show what changed: the changelog file the package ships, or the release notes published with the release. Prefers the file on disk for the installed version; falls back to the notes.

ketch changelog ripgrep            # installed version
ketch changelog ripgrep --latest   # newest release instead
ketch changelog ripgrep --file     # only the shipped file
ketch changelog ripgrep --release  # only the published notes

ketch search <QUERY>... [-n <LIMIT>]

Search GitHub (and searching plugins) for installable repositories.

ketch search fuzzy finder
ketch search ripgrep -n 5

Reproduce

ketch lock [--file <FILE>] [--check]

Write ./ketch.lock from what is installed — the reproducible record of the machine's tools, pinned to exact releases. See LOCKFILE.md.

ketch lock              # write ./ketch.lock
ketch lock --check      # has the tree drifted from it?
ketch lock -f tools.lock

ketch sync [--file <FILE>] [--prune] [--dry-run]

Install everything ketch.lock names, at the versions it names. A package the lockfile does not mention is left alone unless --prune is passed (which asks first, since it can lose work).

ketch sync                 # missing or drifted packages only
ketch sync --dry-run       # show the +/~/− plan, change nothing
ketch sync --prune --yes   # also remove extras, without asking

History

ketch history [PKG] [-n <LIMIT>] [--json]

Show what was installed, upgraded and removed, newest first. Omit the package for the whole tree in one timeline.

ketch history
ketch history ripgrep -n 10

ketch stats [--json]

Summarise everything ketch has recorded in stats.db.

ketch stats
ketch stats --json

Registry and sources

ketch update

Refresh the package registry into ~/.ketch/registry. (For installed packages, see upgrade.)

ketch update

Runs automatically at the start of install and upgrade unless auto_update is false in ~/.ketch/config.toml (KETCH_AUTO_UPDATE=false).

ketch registry validate [DIR] [--fixture <DIR>] [--changed <NAME>] [--json]

Validate a registry tree the way the pre-push hook does: every ketch.toml parsed and checked, plus name collisions. --fixture offline-installs entries against local files as extra proof.

ketch registry validate
ketch registry validate ./ketch-registry --fixture ./fixtures

ketch registry status [--json]

Show the local registry copy's age and source, without fetching.

ketch registry status

ketch registry push [--file <FILE>] [--registry <REPO>] [--dry-run] [--yes]

Compare this project's ketch.toml with the registry's copy and open a pull request with it. Opens straight away for a new package; shows a diff and asks for an update; reports unchanged and opens nothing when identical. See REGISTRY.md.

KETCH_GITHUB_TOKEN=... ketch registry push
ketch registry push --dry-run

ketch plugin list [--json] / ketch plugin dir

Show discovered source plugins (ketch-source-<scheme> in ~/.ketch/plugins and on PATH), or print the plugins directory. See PLUGINS.md.

ketch plugin list
ketch plugin dir

Environment

ketch doctor [--fix] [--json]

Check the environment and the install tree: version, PATH setup, platform checks, log, registry age, store against state.json. Exits non-zero when a check fails. On Windows it also warns about user PATH entries that name a ketch bin dir whose folder is gone — this root's, or any .ketch\bin.

ketch doctor
ketch doctor --fix    # repair what can be repaired (the PATH setup)

ketch path [install|uninstall|status]

Put the ketch bin directory on PATH. Bare ketch path shows the status table; that is the default subcommand.

ketch path                  # bin dir, PATH state, per-shell table
ketch path install          # edit shell startup files (asks per shell)
ketch path install --print  # print the line to add by hand
ketch path install --all    # act on every known shell
ketch path uninstall        # take the block back out again

ketch config create [--file <FILE>] [--force] [--yes]

Write a package config (ketch.toml) by answering questions — source, name, bin entries, asset patterns — then preview and write the file. A package that links binaries must name the command it puts on PATH, so the first bin entry is asked for rather than offered; its name defaults to the package name. Answers can be piped on stdin, one per line. See MANIFESTS.md.

ketch config create
ketch config create --file ./ketch.toml --yes

ketch config reset [--yes]

Write config.toml in the ketch root with the compiled defaults. Asks first unless --yes; backs the existing file up beside itself as config.toml.bak-<unix-seconds> unless it is missing or already matches a sibling backup.

ketch config reset
ketch config reset --yes

ketch completions <SHELL> [--install]

Print a shell completion script, or install it into the shell's user completion directory with --install.

ketch completions zsh > _ketch
ketch completions bash --install

The bash and PowerShell scripts also complete package names: installed ones after uninstall, upgrade, pin, unpin, link, unlink, info, releases, why, changelog and rollback, and names from the local registry copy after install and search. It asks the binary for them with the internal ketch __complete <installed|registry> [PREFIX], which reads the state file and the registry already on disk and never touches the network. A --root earlier on the command line is honoured.

--install writes the bash script to ${XDG_DATA_HOME:-~/.local/share}/bash-completion/completions/ketch, where bash-completion 2 loads it on first use. On macOS that needs more than the system shell: /bin/bash is 3.2 and bash-completion 2 wants bash 4.2 or newer. Install a current bash and bash-completion 2 (with Homebrew: brew install bash bash-completion@2), make that bash your login shell, and source bash-completion's bash_completion from ~/.bashrc as its caveats say. The script itself also runs under bash 3.2, so eval "$(ketch completions bash)" in ~/.bashrc works without either.

On Windows, --install (and ketch self install, which installs every shell's script) also switches completion on, since neither shell loads a completion directory by itself:

  • PowerShell. The script goes to Documents\PowerShell\Completions\ketch.ps1, and a block between # >>> ketch >>> and # <<< ketch <<< in the CurrentUserAllHosts profile of PowerShell 7 (Documents\PowerShell\profile.ps1) and Windows PowerShell 5.1 (Documents\WindowsPowerShell\profile.ps1) dot-sources it. Documents is the folder PowerShell reports, so a OneDrive redirect is followed. A profile that does not exist yet is created only when that edition is installed and its execution policy runs local scripts; otherwise ketch says why it left it alone.
  • cmd. cmd has no programmable completion, so it gets doskey macros: ki (ketch install), ku (ketch upgrade), kl (ketch list) and kun (ketch uninstall), each passing its arguments on. They live in <root>\share\ketch\ketch.doskey, loaded by doskey /macrofile="…" appended to HKCU\Software\Microsoft\Command Processor\AutoRun with & after whatever AutoRun already runs.

ketch self uninstall takes the profile blocks out (deleting a profile that held nothing else) and removes exactly its own command from AutoRun, leaving the earlier value as it was, or deleting the value when it held only ketch's.

ketch itself

Install this release of ketch as a package, into the store and bin dir. What the curl/PowerShell/Homebrew installers run.

ketch self install

ketch self upgrade [--dry-run] [--force] [--pre] [--yes]

Upgrade ketch to the latest release. Aliased as ketch self update. A ketch installed with mise and never self installed is mise's to upgrade: this refuses, and names mise upgrade.

--pre takes the newest release, prereleases included, instead of GitHub's latest release. The macOS app's desktop-v* releases are never taken. The installers take the same flag: install.sh --pre and install.ps1 --pre. Without it nothing changes.

ketch self upgrade
ketch self upgrade --dry-run
ketch self upgrade --pre

ketch self version

Print the running version and where it lives (target, root, binary, PATH).

ketch self version

ketch self uninstall [--keep-packages] [--dry-run] [--yes]

Remove ketch and everything it installed, permanently. Lists what it is about to delete and asks first. --keep-packages removes only ketch. A ketch installed with mise also asks whether to run mise unuse -g for its own copy; --yes answers that too. On Windows the running ketch.exe cannot delete itself, so the rest of the root is removed by a background process once ketch has exited.

The root's own directories (bin, store, cache, …) are emptied only when ketch created the root — it then holds a .ketch-root marker — or the root is the default ~/.ketch. A root that already existed, such as ~/.local or /usr/local, and the home directory itself keep everything ketch did not record there.

On Windows it also removes what ketch wrote to the registry. Today that is only the bin dir in the user PATH (HKCU\Environment\Path), written by install.ps1 or ketch path install. The entry is matched however it is spelled: case, quotes, / or \, a trailing separator, or an 8.3 short name. --keep-packages leaves it, as it leaves the shell blocks, because the packages still in the bin dir need it. ketch never registers itself in Apps & Features, so there is no entry there to remove.

It also removes the PowerShell profile blocks and the cmd AutoRun addition that switch completion on — those with --keep-packages too, since both load ketch itself.

ketch self uninstall --dry-run
ketch self uninstall --keep-packages