yups explains shell commands, prints the #_? marker in ANSI 256-colour 214,
and manages its own installation.
$ yups # prints #_? in colour 214
$ yups -- ls -al /var/cache/man/
$ yups -- "ls -l || ps aux"
$ yups --help # help with the options available so far
$ yups --install-yups
$ yups --uninstall-yups
$ yups --update-yups-
-- <command...>(oryups <command...>): explains a shell command line. It tokenizes simple or compound pipelines (connected by||,&&,;,|,|&,&), unpacks clustered flags (-al->-a,-l), identifies wrappers (sudo,time...), inspects positional arguments on the filesystem, and fetches documentation prioritizing<command> --helpbefore falling back tomanandwhatis. Local basic documentation is displayed immediately. When a command or flag is not found locally, or when the command line includes a comment (#...), it announces the query and connects to the configured Ollama inference endpoint via direct HTTP. Shell comments are interpreted as user goals or questions, allowing the LLM to explain discrepancies and suggest optimal commands along with low-cost system context (OS distribution, working directory structure, referenced file snippets, and recent shell history). If the LLM suggests a command,yupsprompts for action:[Yes/no/edit/modifications](yexecutes immediately in subshell,naborts,eenables interactive inline editing with cursor navigation,msends feedback/followup to the LLM in a multi-turn conversation). If Ollama is unreachable,yupsreports the connection error and, if unconfigured, suggests runningyups --install-yups. -
--help: shows the help. -
--version: printsyups vX.Y.Z(overridden at build time; a plaingo buildreportsdev). -
--install-yups:- If the executable is already reachable through any
PATHdirectory, it reports that it is already installed. - Otherwise it checks whether a command named
yupsalready exists in one of the well-known system binary directories (/usr/local/bin,/usr/bin, ...); if so, it reports that it is already installed. - When several coexisting copies show up anywhere, the user is informed of
all discovered duplicates while operating with the first instance found in
PATH(matchingwhichbehavior). - Then it looks for the first
PATHdirectory where the current user can write. When there is none and the user has administrator privileges (membership of a well-known administrator group —sudo,sudoer,sudoers,wheeloradmin—, or passwordless sudo rights) it suggests repeating the previous command withsudo !!; otherwise it reports that the installation is not possible. - If everything is fine, the executable is copied into that directory, prompts
for the Ollama inference endpoint (
http://localhost:11434by default), automatically probes/api/tagsto discover and configure models, initializes~/.yups/config.toml, and downloads community cheatsheets (tldr-pages,navi,cheat.sh,cheat) into~/.yups/cheatsheets/.
- If the executable is already reachable through any
-
--uninstall-yups:- If there is no
yupscommand anywhere (PATH plus the well-known binary directories), it reports that it is already uninstalled. - It locates every directory holding a copy of the command. When there are several, it asks whether to uninstall for all users; declining keeps the system-wide copies and removes only the ones inside the user home.
- It deletes every target copy the current user has permission to remove.
- If some copy could not be removed and the user has administrator privileges
(see above),
sudo !!is suggested. - Finally, when
~/.yupsexists, it asks whether to keep configuration files and request history (default: yes). If the user declines, and has administrator privileges, it asks whether to delete the configuration and history for all users or only for the current user. Piped or closed stdin takes the defaults of every question.
- If there is no
-
--update-yups:- Loads
~/.yups/config.toml. A missing file means defaults (with a warning and an offer to run the per-user installation); a corrupt file aborts with an explicit error. - Queries the latest release from the primary repository (Forgejo) and, when unreachable, from the fallback (GitHub).
- If the release version is older than the current version (or if running a
devbuild), it informs the user and prompts for confirmation before performing a downgrade. If the versions match, it reports that it is up to date and exits. - Downloads the platform archive plus
checksums.txt, verifies the sha256 digest, extracts the archive into an ephemeral staging directory (yups-*.kk) and self-validates the new binary (--versionmust report exactly the release tag). - It replaces its own process image with the new binary (
--update-apply), which atomically substitutes the keeper instance found inPATH(temporary.kkfile in the target directory,chmod 0755, rename), updatesconfig.version, applies any pending migrations tracked in~/.yups/state.toml, and safely cleans up the ephemeral staging directory.
- Loads
Exit codes: 0 success, 1 error, 2 wrong usage.
Where does yups look, and what does it consider an administrator?
| Decision | Value | Why |
|---|---|---|
| Install target | First writable directory of the current PATH |
Respects whatever the user configured. |
| "Already installed" check | Every PATH directory plus /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin |
Catches copies reachable through non-default PATHs. |
| Administrator groups | sudo, sudoer, sudoers (Debian family), wheel (Fedora/RHEL/Arch/openSUSE), admin (historic Ubuntu, macOS) |
The default administrator group of the mainstream distros. |
| Administrator probe fallback | sudo -n true succeeds |
Covers NOPASSWD sudoers and root on single-user systems where no group matches. |
| Write-permission probe | Create + remove a temporary .kk file in the directory |
More reliable than access(2) with ACLs, read-only mounts or sticky bits. |
| Config file | ~/.yups/config.toml: version, yups-repo, yups-repo-fallback, [inference] (endpoint, default-model, advanced-model, available-models, disabled), [limits] (llm-timeout-seconds, tool-execution-timeout-seconds, max-tool-turns, max-tool-output-bytes, advanced-multiplier) |
version records the installed version; organized in sections with backward-compatibility for older flat configs; a corrupt file is an explicit error instead of silent defaults. |
| Execution logs | ~/.yups/logs/: sessions.log (summary index) and session-<timestamp>-<pid>.log (full execution and Ollama exchanges) |
Records step-by-step resolution decisions and raw request/response payloads for debugging and auditing. |
| Inference endpoint | Default: http://localhost:11434 (Ollama HTTP API) |
Queried directly via HTTP without third-party libraries; models discovered via /api/tags with smart code-model prioritization. |
| Self-update sources | Primary yups-repo (Forgejo), fallback yups-repo-fallback (GitHub) |
Forgejo is the canonical source of truth; GitHub only rescues outages. |
| Binary swap | Copy to temp .kk file in the target dir, chmod 0755, rename |
Same-directory rename is atomic and can never fail with EXDEV. |
| Multi-location anomaly | First occurrence in PATH (matching which); informs user of duplicates |
Operates consistently with the binary the user would execute from shell. Duplicates are left untouched. |
| Uninstall questions | Defaults: uninstall for all users, keep configuration (~/.yups) |
Non-destructive defaults; prompted deletion allows single-user or all-users cleanup for administrators. |
Requirements: Go >= 1.25 and, for the integration tests, Docker.
The repository ships a small Makefile that wraps the everyday commands (build,
static checks, tests) behind short, memorable targets. make is not required by
the Go toolchain — every target is a plain one-line go command (see the next
section) — but it keeps the canonical invocations written down exactly once:
build tags, test flags, distro selection and the output filter
(scripts/pretty-tests.sh) that colours PASS/FAIL, splits the CamelCase
test names (TestInstallTwiceReportsAlreadyInstalled →
Test Install Twice Reports Already Installed), shows subtests by their leaf
name only, indents nested subtests and, on an interactive terminal, shows the
parallel suite as a live view whose summary lists only FAIL/SKIP results (or
reports that there is nothing to review).
$ make build # builds ./yups
$ make test # go vet + lint + unit tests
$ make test-integration # integration tests, every distro (default)
$ make test-integration DISTRO=fedora # one specific distro
$ make test-integration DISTRO=fedora,arch # several distrosThe integration scenarios run in parallel — every scenario gets its own
throwaway container, so the whole four-distro matrix costs little more than a
single distro. Concurrency is capped with -parallel 8; lower it if your
machine starts sweating. The pretty printer repaints the grouped view live; set
REPAINT=never for plain streaming output.
The integration target selects the systems under test with the make variable
DISTRO, which it translates into the YUPS_TEST_DISTRO environment variable
consumed by the tests themselves.
If you prefer to skip make, these are the plain commands it wraps.
Build:
$ go build -o yups .Static checks and unit tests (no Docker needed; unit tests live next to the code
in internal/app and use an in-memory fake of the operating system):
$ gofmt -l .
$ go vet ./...
$ golangci-lint run # optional; skipped by `make test` when missing
$ go test -v ./...Integration tests (integration/, build tag integration) build a linux binary
and run every scenario inside stock distro containers — root installs, plain
users without permissions, users in administrator groups, writable ~/bin
directories, repeated installs, partial uninstalls, etc. Every distro runs by
default:
$ YUPS_TEST_DISTRO=all go test -tags integration -parallel 8 ./integration/ -count=1 -vSelect fewer systems with the YUPS_TEST_DISTRO environment variable (comma
separated names; unset means all):
$ YUPS_TEST_DISTRO=fedora go test -tags integration ./integration/ -count=1 -v
$ YUPS_TEST_DISTRO=fedora,arch go test -tags integration ./integration/ -count=1 -vThere is a single set of workflows, .github/workflows/, executed by both
remotes (Forgejo reads .github/workflows too — as long as no
.forgejo/workflows directory exists):
- Every push / pull request:
ci.ymlbuilds, vets, lints, runs the unit tests and the full integration matrix. GitHub hosted runners provide the docker daemon; self-hosted runners expose theirs throughDOCKER_HOST(see configuration/forgejo-runner.md — jobs never need privileged mode). - Tag push
vX.Y.Z:release.ymlruns the full suite again and only then invokes goreleaser: linux amd64/arm64 tarballs, checksums and changelog. Publishing target depends on where the tag lands — GitHub (.goreleaser.yaml) or the self-hosted Forgejo instance (.goreleaser-forgejo.yaml) — using in both cases the automatic per-run token; no user-defined secrets are required.
What a self-hosted worker needs (labels, docker daemon, security model) is documented in configuration/forgejo-runner.md; other environment-specific knowledge lives next to it.
yups integrates community-driven command documentation and cheatsheets to give users and local LLMs fast, offline, and accurate contextual assistance. We gratefully acknowledge and credit the creators and communities behind these indispensable resources:
- tldr-pages: Collaborative cheatsheets for console commands.
- navi cheatsheets by Denis Isidoro and contributors.
- cheat.sheets by Igor Chubin and the cheat.sh community.
- cheatsheets by Chris Allen Lane and the cheat community.