Real CLIs tested with atago
These real programs run their end-to-end suites on atago; most were migrated from ShellSpec. The generated behavior docs live in doc/e2e/.
Every suite here was written by the atago project on its own initiative and runs in atago’s own CI — the point is to exercise atago against real programs of every shape. The third-party projects listed below are not affiliated with atago, did not commission these tests, and these specs are not their official test suites.
Programs maintained by the author, starting with atago itself (test/e2e/tools/):
| Tool | Feature | Specs | Docs |
|---|---|---|---|
| atago | atago tested by atago: the self-hosted specs run the real built binary in CI (make e2e). | specs | docs |
| gup | Updates and manages the Go command-line tools in $GOBIN. | specs | docs |
| sqly | Runs SQL against CSV/TSV/LTSV/JSON/Parquet/Excel/ACH/Fedwire files. | specs | docs |
| truss | Image transformation (convert/resize/re-encode). | specs | docs |
| iso8583tool | Debugs and inspects ISO 8583 payment messages. | specs | docs |
| jose | Signs and encrypts with JOSE. | specs | docs |
| career | Renders résumé PDFs from a single YAML file. | specs | docs |
| mimixbox | Packs many Unix commands into one BusyBox-style binary. | specs | docs |
| mobilepkg | Inspects Android packages for metadata and security findings. | specs | docs |
Third-party programs (test/e2e/thirdparty/). atago runs each as an unmodified binary and ships only its own spec YAML — no third-party code is copied or redistributed, which every listed license permits:
| Program | Feature | License | Specs | Docs |
|---|---|---|---|---|
| git | Version control — runs in CI on all three OSes: init/add/commit and status, plus diff --exit-code as a status contract, exact --porcelain codes, a fast-forward merge and a real conflict, stash and patch round trips proven byte for byte, .gitignore keeping a file out of the index, a local clone reproducing the same commit, and the documented failures (no repository, duplicate tag, nothing staged). | GPL-2.0 | specs | docs |
| jq | JSON processor: stdin-driven filters, –arg injection, and its documented exit-code contract (1 for -e false, 3 for a bad program, parse failures). | MIT | specs | docs |
| fzf | Interactive fuzzy finder driven inside a real pseudo-terminal: expect/send sessions type queries, multi-select, and abort (exit 130); –filter pins the non-TTY contract. | MIT | specs | docs |
| fx | JSON viewer that is a filter when given a reducer and a full-screen tree when not. The filter half pins the JavaScript semantics a jq user gets wrong (a missing key is undefined on stderr with exit 0, dereferencing through it is exit 1), exit() choosing the process’s code, and the identity path keeping number literals a float64 round trip would rewrite; parser flags cover YAML/TOML/raw/slurp, filename-driven format detection, --strict rejecting comments and trailing commas, and base64 and YAML round trips. save is pinned with exhaustive changes: deltas — in-place rewrite, idempotence, the no-file and symlink refusals — alongside .fxrc.js loaded from the workdir and from a sandboxed home that stays unwritten. The viewer runs in a pty: folding, regexp search reporting the cursor’s JSON path, and the split that makes fx data.json > picked.txt work, with the interface drawn on stderr and only the chosen value on stdout. | MIT | specs | docs |
| gum | Shell-script UI toolkit: non-interactive stdout contracts (version-check, join, table, log) plus TTY prompts and pickers (choose, filter, confirm, input, file, pager) driven over a real terminal. | MIT | specs | docs |
| Helix Editor | Modal editor workflows in a real TUI: append/insert editing, line-open-above/below, undo-after-delete, nested-path saves, multi-buffer next/previous/edit-both flows, copy-via-:w path, and UTF-8 filename/content saves under isolated Helix config/cache homes. | MPL-2.0 | specs | docs |
| lazygit | Git TUI on a real repository: staged/unstaged/untracked transitions (including second-item selection), editor-driven commits (including pre-staged changes), discard accept/cancel flows, branch creation including slash names and canceled prompts, stash create/pop plus cancel paths, and the invalid nested-path startup contract. | MIT | specs | docs |
| htop | Full-screen alternate-screen TUI as a pty screen-rendering testbed: the rendered frame shows the meters and column header, q quits with a clean lifecycle, and an unknown flag is rejected before the TUI opens. | GPL-2.0 | specs | docs |
| yazi | Terminal file manager TUI: rendered-screen hidden-file toggles and live filtering; file/dir create + rename including space-bearing names; explicit ENTRIES... startup for files, directories, hidden paths, and multi-tab launches; chooser-file and cwd-file handoff across tab switches, close flows, and explicit selection/cancel-selection paths; absolute/relative symlinks plus hardlinks via the physical Ctrl+- key; single-file and directory copy/move flows into sibling directories, including overwrite/collision suffixing and spaced-path variants; visual/space/ctrl-a/ctrl-r selection semantics across file and directory operations; trash vs permanent delete into an isolated sandbox home, including canceled dialogs; modified-time plus alphabetical/extension/size/natural sort checks on both the rendered list and chooser output; content search via S; file-info toggles via Tab; arrow-key navigation and tab reordering/switching; cancel-yank flows via X/Y; and blocking : shell-command integration for file, dir, symlink, and hidden-file mutations. | MIT | specs | docs |
| redis | Server+client pair: redis-server as a scenario service (readiness by log and by port), redis-cli round-trips (PING/SET/GET/INCR/TTL), error contracts, and a polled graceful shutdown. | RSALv2/SSPLv1 | specs | docs |
| hugo | Scaffold + build + serve in one binary: hugo new site tree assertions, a theme-less --minify build, and hugo server bootstrapped in suite.setup then queried over HTTP. | Apache-2.0 | specs | docs |
| openssl | Cryptography toolkit: exact digests, keygen → sign → verify (and tamper detection), encrypt/decrypt round-trips with the wrong-password failure mode, self-signed certificates. | Apache-2.0 | specs | docs |
| sqlite3 | Embedded-database shell driven as a real binary: one-shot SQL, -json/-csv output modes, .dump → .read rebuilds, .import from CSV, bad-SQL diagnostics. | Public Domain | specs | docs |
| caddy | Self-hosted web server, booted from an authored Caddyfile and queried over HTTP. | Apache-2.0 | specs | docs |
| coredns | Self-hosted DNS server: an authored zone queried with real dig — authoritative answers, CNAME chasing, NXDOMAIN/REFUSED, and the health plugin over HTTP. | Apache-2.0 | specs | docs |
| gitea | Self-hosted git service: booted with SQLite, administered via its CLI, driven over the REST API (repos, commits, issues), then cloned with real git. | MIT | specs | docs |
| gotify | Self-hosted notification server: app provisioning, token-authenticated pushes, and the app icon uploaded as real multipart/form-data, downloaded back, and verified as a PNG. | MIT | specs | docs |
| grafana | Self-hosted observability platform: health/build info, the login redirect asserted with follow_redirects: false, and a dashboard + datasource lifecycle over the REST API. | AGPL-3.0 | specs | docs |
| mailpit | Self-hosted email testing: messages delivered over real SMTP (stock curl), then asserted via the REST API — capture, search, MIME attachments, teardown. | MIT | specs | docs |
| openfga | Fine-grained authorization CLI + server, in parity with the fga CLI’s own integration suite: local fga model test runs (glob patterns, the store-file reference containment contract with its --allow-external-files opt-in, the type-count limit), and a real openfga server driven through store creation, JSON/JSONL tuple writes and deletes (including a conditional tuple), and store import — each response asserted as JSON. | Apache-2.0 | specs | docs |
| minio | Self-hosted S3-compatible object storage: full object lifecycle via mc, versioning, anonymous bucket policies, S3 XML error contract. | AGPL-3.0 | specs | docs |
| aws-cli | The AWS CLI driven against a local MinIO S3 endpoint — a cloud CLI tested offline: bucket/object lifecycle, byte-identical round-trip, head-object JSONPath, presigned-URL fetch, missing-key error contract. | Apache-2.0 | specs | docs |
| python3 | The REPL as an interactive pty testbed: prompt detection, multi-exchange expect/send, EOF exit, traceback recovery, and TTY branching — a copy-paste template for testing your own REPL. | PSF-2.0 | specs | docs |
| ssh-keygen | OpenSSH key generation: non-interactive and interactive-passphrase (pty) key pairs, generated-file asserts, the exact fingerprint contract, and verification-failure exit codes. | BSD | specs | docs |
| ffmpeg | Media pipeline: lavfi video synthesis, ffprobe stream JSON (JSONPath), frame extraction verified with image assertions (format/dimensions/pixel similarity), a webm transcode, and error contracts. | LGPL/GPL | specs | docs |
| pandoc | Document conversion: markdown→HTML, a docx artifact verified by unzipping its OOXML, a stdin→stdout filter (via stdin: {file:}), the JSON AST queried with JSONPath, metadata-driven standalone output, a markdown↔HTML round-trip, and the unknown-format and missing-input error contracts. | GPL-2.0+ | specs | docs |
| ImageMagick | Image conversion and inspection: every output decoded and checked as an image (format sniffed from bytes, pixel dimensions, alpha), aspect-ratio versus forced resize, a PNG↔PPM round trip verified both by atago pixel comparison and by magick compare -metric AE as an independent oracle, metadata stripping that leaves the picture untouched, the identify JSON contract, and the missing/corrupt-input failure contracts. | ImageMagick (Apache-2.0 style) | specs | docs |
| zstd | Compression round trips proven byte for byte with equals_file (including an empty file and NUL bytes), the input kept unless --rm, an existing archive never silently overwritten and -f overriding that, -t integrity verification of a good and a corrupt frame, a failed decompression leaving no partial output, the stdin→stdout pipeline, and the missing-input and unknown-option failure contracts. | BSD-3-Clause / GPL-2.0 | specs | docs |
| zip / unzip | Extracted trees asserted with dir: in every mode (recursive membership, a file-only count, a glob, a snapshot manifest), round trips proven byte for byte with equals_file including an empty file and NUL bytes, -j flattening as the metamorphic pair of the tree case, the overwrite decision (declined by default, -n, -o), archive update and entry deletion, the documented exit codes 0/1/9/10/11 and zip’s 12, and the zip-slip refusal: a ../ or absolute entry is stripped, reported, and kept inside the destination, proven by changes: rather than by the warning text. | Info-ZIP | specs | docs |
| Ghostscript | PostScript→PDF→text/raster pipeline asserted on the documents themselves: page count, the text really inside them, and Info-dictionary metadata via pdf:, plus the rasterized page via image:. Relationships are pinned too — one selected page keeps its own text and drops the other, merging sums the pages, txtwrite agrees with the extracted text, and a repeat conversion yields identical text — alongside the missing-input and unknown-device failures (whose explanations go to stdout) and the surprise that a failed conversion still leaves a PDF behind. | AGPL-3.0 | specs | docs |
| terraform | The multi-exit-code IaC CLI, fully offline via the builtin terraform_data resource: init/validate/plan/apply/destroy, the plan -detailed-exitcode 0/1/2 contract, state JSON, and fmt -check exit 3. | BUSL-1.1 | specs | docs |
| age | Modern file encryption: keygen, binary-safe encrypt/decrypt round-trips, armored (PEM) output, interactive-passphrase mode (pty), and clean failure semantics. | BSD-3-Clause | specs | docs |
| sops | Secrets encryption with age as the key backend: value-only encryption that keeps keys readable and adds a MAC, an encrypt→decrypt value round-trip, single-field --extract, wrong-key failure (128), a tampered ciphertext failing the MAC (25), and --encrypted-regex scoping which keys are encrypted. Every age key is generated in the workdir. | MPL-2.0 | specs | docs |
| GnuPG | OpenPGP contracts pinned as round trips and refusals: encrypt/decrypt returns the original bytes and the ciphertext never carries the plaintext, ASCII armor decodes back byte for byte, a single flipped byte and a keyring without the secret key are refused rather than approximated, a detached signature stops verifying the moment the file changes, and symmetric encryption answers only to its own passphrase. Exit codes are asserted at all three levels gpg uses (0, 1 for a bad signature, 2 for everything else) alongside the --status-fd protocol (GOODSIG, BADSIG, NO_SECKEY, DECRYPTION_FAILED) that integrations actually read. Keys are generated per scenario in the workdir; key lookup is disabled so nothing reaches the network. | GPL-3.0 | specs | docs |
| kustomize | Declarative Kubernetes manifest rendering, fully offline and deterministic: build transforms (prefix/namespace/labels/images), generator content-hash suffixes (and disabling them), strategic-merge and JSON6902 patches, the load-restrictor security contract, byte-identical repeated builds, and the create/edit/fix authoring side effects. | Apache-2.0 | specs | docs |
| kubectx / kubens | Context and namespace switching against throwaway kubeconfigs, with no cluster contacted: the whole of both upstream Bats suites reproduced, plus the stream split (names on stdout, status lines on stderr), the kubeconfig on disk as the oracle for every switch, rename, and delete, exhaustive changes: deltas proving a refused name writes nothing, the previous-context and per-context previous-namespace state landing under a sandboxed home, the non-atomic batch delete, kubens --force, and the fzf picker driven in a pseudo-terminal. | Apache-2.0 | specs | docs |
| bats-core | The Bash test runner, checked from outside the thing it runs: the TAP plan and ok/not ok lines byte for byte, exit 0 for green against 1 for red, and the gather-tests failures (a missing file, a file Bash cannot parse, an unmet version guard) that move the whole report to stderr and leave stdout empty. Selection is asserted on both halves — --count proving the bodies never ran, --filter as a regexp rather than a substring, tag AND/OR/negation, a directory walked flat or recursively — including the stateful --filter-status, whose run-log ledger bats refuses to create for you and then uses to replay last run’s failures and the tests added since. Formatters are pinned per shape: tap13 YAML blocks, JUnit on stdout and as a written report, the per-test files of --gather-test-outputs-in, timing, trace, quote and file-reference styles, and the pretty formatter driven on a rendered terminal because it exists nowhere else. The harness around a test body is pinned with an append-only log — setup/teardown ordering, a failing setup that stops the body while teardown still runs, the run helper with its captured status and BW02 warning, and the per-test tmpdir cleaned up unless --no-tempdir-cleanup. | MIT | specs | docs |
| ShellSpec | The BDD test framework for POSIX shells, checked from outside its own ShellSpec suite. Its three-level exit contract is pinned at every level — 1 for a command line it cannot use, 101 for specs that failed, 102 for a specfile that will not parse — along with both overrides, and with the two defaults worth knowing: an example that asserts nothing is a failure, finding no examples is not. Selection is asserted on both halves (counting and listing without running, a dry run that reports a failing example as ok, tags, focus, line and id ranges, the file pattern, and the surprise that --example matches a whole name rather than a substring), and quick mode gets its full state machine: the ledger appearing, the replay narrowing to the failure, the ledger emptying once it passes. Every formatter is pinned in its own shape, --output report files and --profile and the --xtrace log alongside them, and --random is recorded as it behaves — verdict intact, TAP plan line malformed. The specfile side is proven with an append-only log: hook ordering, Include with call against run, Data, Parameters, mock scoping, the --sandbox that removes PATH, the runnable --init scaffold, and the report colors checked on a real terminal. | MIT | specs | docs |
| rbenv | Ruby version selection, tested without a Ruby: a version is a directory, so each scenario builds a root of its own and drives the real resolution rules against two-line executables. The state machine is pinned end to end — the file global writes, the .ruby-version local writes, the environment variable that outranks both, the origin reported for every choice, --unset deleting rather than blanking, and the refusals that leave every one of those files untouched, proven with exhaustive changes: deltas. The executable half is asserted the way a user meets it: ruby on PATH is the shim, and it answers 3.2.0 or 3.3.1 depending on the directory while PATH never changes. Also rehash adding exactly what is new, exec passing arguments through and returning 127 for a command that is not there, a plugin hook seeing the resolved version, the shell integration init - bash prints, and init editing one profile inside a sandboxed home and nothing on the second run. | MIT | specs | docs |
| git-secrets | Secret scanning and the hooks that enforce it, with no real credential anywhere in the suite: the patterns are invented per scenario, which exercises every rule the tool has. Where a pattern is stored (the repository git config), what a match looks like and that all of it — finding, error, and the list of mitigations — goes to stderr while stdout stays empty, the two ways to allow a false positive (config and a committed .gitallowed), --literal escaping metacharacters so they mean themselves, and the exit codes 0/1/2/129 including the silent exit 0 a directory scanned without -r comes back with. The hooks half is asserted through git: the three installed hooks and their contents, the refusal to overwrite, a commit blocked with the history as the oracle rather than the message, a commit refused for its message alone, and --no-verify letting a secret into history where --scan-history then finds it by commit. The installer exiting 127 on a say it never defines is pinned as observed. | Apache-2.0 | specs | docs |
| getoptions | A generator for POSIX-shell option parsers, asserted on what it emits rather than on the text it emits: every generated parser is sourced and run. The generator half covers the printed example going back in as input, indentation and comment options changing the artifact while both still parse, the library with and without shellcheck directives, and the embed round trip — a script with the parser embedded runs with getoptions nowhere on PATH, and erasing the block gives the original file back byte for byte. The generated parsers are then driven the way a command line is: long and short forms, clustering, --{no-} negation and the +f form, counters, optional arguments with their on: default, -- ending the options, abbreviations accepted while unambiguous and refused with the candidates listed when not, and one message per mistake (unrecognized option, missing argument, pattern, custom validator). Two traps are pinned as observed: a definition generated without the argument it references writes an empty parser at exit 0, and a validate: naming an undefined function refuses valid input at 127. | CC0-1.0 | specs | docs |
| shdotenv | A .env loader whose promise is that the file cannot run anything, asserted the way a security claim should be: three files that try to run a command are each refused, and the proof is that the files they would have created do not exist — with a value carrying ; touch … accepted as text and still inert while a real command runs with it in the environment. Around it, the strict defaults (a reference to an unset key is an error, two files defining the same key is an error), the precedence between file and environment including what --overload changes and the omission that mirrors it in the printed output, and the observation that a missing --env path is silently exit 0. The dialect matrix is exercised as a differential — a \n that stays two characters under POSIX and becomes a newline under Ruby, Node and Go, a spaced key that POSIX and Docker refuse and Ruby accepts — and all six output formats are pinned exactly, alongside an export round trip. --sort is pinned as observed: it reorders, but not into sorted order, and is ignored entirely with --name-only. | MIT | specs | docs |
| git-open | The translation from a git remote to a web address, asserted as a matrix: ssh, https, the git protocol, an ssh port, a missing .git suffix, a GitLab subgroup, a gist, and Bitbucket — whose branch pages live under /src rather than /tree. Around it, the default branch opening the repository itself while another branch opens the branch page, a slash kept as a path and a # percent-escaped, a detached head opening its commit, the --commit/--file pages and the refusal for a file the repository does not track, a remote and branch named as arguments, and the exit 129 git returns for a command line it cannot use. One scenario names a script as BROWSER and proves from the log it writes that the address is handed over as a single argument. Two behaviors are pinned as observed: --issue on the default branch answers with the repository page, and a repository with no remote configured produces https://origin/origin at exit 0. | MIT | specs | docs |
| git-extras | A collection of git subcommands, asserted on the repository afterwards rather than on what they printed. The reporting half pins summary, count, effort and authors against a repository built commit by commit — including the finding that the author tables of summary and count --all come from git shortlog, which reads standard input when it is not on a terminal, so through a pipe the headings are there and the authors are not; the same commands are run in a pseudo-terminal, where the tables appear. The writing half pins the branch lifecycle (create, rename, delete, and the merged-branch sweep that keeps unmerged work), undo taking back a commit while leaving it staged, ignore appending to the file git actually reads, alias writing a real git alias, and changelog collecting the commits since the last tag — and prepending a second copy of that section when run again. delete-branch printing an error about a remote-tracking branch that never existed, at exit 0, is pinned as observed. | MIT | specs | docs |
| transcrypt | Transparent file encryption inside a git repository, asserted as the difference between two views of one file: the working tree holds the plaintext while git show HEAD:<file> holds an OpenSSL envelope with no trace of it, and a file the pattern does not name is stored verbatim. The properties that make the scheme usable are pinned too — re-saving identical content leaves the tree clean, a real change produces a plaintext diff that --stat still calls binary, and two paths with identical content do not encrypt alike. The workflow half clones the repository for real: the copy carries ciphertext until it is given the password, the wrong password is caught rather than left as garbage, and --uninstall is driven in a pseudo-terminal for both answers — accepted, it removes the configuration and the filter attribute while leaving the plaintext and the already-encrypted history alone; declined, it changes nothing. | MIT | specs | docs |
| nb | A note-taking CLI that keeps every notebook as a git repository, so the oracle for each command is the git log beside the files: add, edit and delete are asserted as commits ([nb] Add:, [nb] Edit:, [nb] Delete:) on top of the notebook’s own Initialize. The first run is pinned in both shapes — refused with an explanation when git has no identity, and completed in a pseudo-terminal where nb asks for the name and email and writes them into the sandboxed home — along with the surprise that the first command initializes the notebook instead of doing what it was asked, at exit 0. Notebooks are covered as separate repositories with scoped identifiers (work:add, [work:1]), use writing the choice to a file, and the refusals: a missing identifier answers on stderr while an unknown subcommand answers with the same sentence on stdout. The listing is uncolored through a pipe and colored on a terminal, with the line-wrap control sequences emitted either way. | AGPL-3.0 | specs | docs |
| mommy | A wrapper that runs your command and comments on how it went, pinned on the two obligations a wrapper has before any of that: the exit status comes back untouched (0, 1, and 42), and the message stays on stderr so the wrapped command owns stdout — with -1 moving it deliberately and -e proving a real pipeline’s output passes through clean. The messages are made deterministic by a config file each scenario writes, so the assertions are about which list was chosen; templates, -s judging a status without running anything, and -p turning a pipeline’s head failure into the verdict are covered too. Three things are pinned as observed: the config variables cannot be set through the environment, the toggle notice arrives on stdout rather than stderr, and while mommy is toggled off the wrapped command’s failure is swallowed with her — mommy false exits 0. | Unlicense | specs | docs |
| asdf | The version manager whose tools come from plugins, tested without downloading anything: each scenario writes its own plugin — bin/list-all, bin/download, bin/install — commits it in the workdir, and adds it from there, so the protocol is what is under test. Pinned are which script asdf calls and what it passes in the environment, the installed program the plugin actually laid down, the download directory cleaned after a success and kept after a failure, a plugin refusing a version being reported rather than second-guessed, and plugin remove taking the installs with it. The resolution half asserts global writing the home file and local the directory’s, current naming which file answered, the shim dispatching by directory with PATH never changing, ASDF_MYTOOL_VERSION outranking the file, which/where/exec agreeing, and the shim’s 126 refusal quoting the exact install command when the selected version is gone. | MIT | specs | docs |
| pyenv | Deliberately only what pyenv does that its rbenv ancestor does not, so the two suites do not repeat each other: a version file naming several versions at once — both reported as current, the first deciding which python runs, and the second still providing a command the first does not have, proven by running it through the shim — plus whence answering which installed version has a command (and exit 1 with nothing printed when none does), aliases listed as versions and excluded by --skip-aliases, and a multi-version file with one missing version failing with the file named. No Python is installed: a version is a directory. | MIT | specs | docs |
| curl | The only suite here whose subject is what a client sends rather than what it prints: every scenario declares an atago mock server, runs curl against it, and asserts the recorded request. -d making a form POST and joining its parts, -d @file stripping the newlines that --data-binary keeps, --json setting Content-Type and Accept and sending the document whole, -F building a multipart body whose boundary is in the header, -H replacing a header curl adds and an empty value deleting one, -u encoding Basic credentials, -X GET sending a body anyway, -G moving the data into the query string, and -L turning a redirected POST into a GET without a body — two recorded requests, which is how the difference is visible at all. Plus a 404 that is exit 0 until --fail, a slow route answered with curl’s own timeout code 28, and a canned response read three times through deterministic:. | curl (MIT-like) | specs | docs |
| actionlint | Static linter for GitHub Actions workflows, offline and deterministic: a clean workflow exits 0 silently, distinct rule categories (undefined needs, unknown runner label, invalid expression) surface as tagged findings on stdout, the JSON format is a structured oracle over them, -ignore suppresses a match, and stdin mode lints piped content. | MIT | specs | docs |
| aqua | Declarative CLI version manager: install pulls a tool from a local registry + local HTTP file server, lands it executable under the managed root, and aqua exec runs it — its essential install→resolve→run flow, hermetic. Plus the offline surface: init (idempotent), policy init, root-dir/completion generators, and exit codes separating a usage error (3) from a runtime error (1, which on an unmanaged command). | MIT | specs | docs |
| ecspresso | Amazon ECS deploy tool: a real deploy against a moto mock AWS endpoint registers a task definition and creates the service, then a changed image rolls it to a new revision — the ECS state change observed with aws ecs describe-services. Plus the offline render surface: env/must_env template functions and defaults, a jsonnet task definition with --ext-str, config resolution, and failure modes at distinct exit codes, checked as structured JSON. | MIT | specs | docs |
| nats | Self-hosted messaging: request/reply through the real broker, JetStream persistence (create → publish → count → purge), the KV store, and the monitoring endpoint. | Apache-2.0 | specs | docs |
| ntfy | Self-hosted push notifications: publish with headers, poll the JSON feed, topic isolation, and deny-all access control unlocked via the admin CLI. | Apache-2.0 | specs | docs |
| prometheus | Self-hosted monitoring: promtool config/rule checks and rule unit tests, the query API, and a self-scrape polled with http retry. | Apache-2.0 | specs | docs |
| pushgateway | Self-hosted metrics gateway: raw text body: pushes scraped back with their group labels, a malformed payload rejected (400) and never ingested, POST-merges-vs-PUT-replaces group semantics, an added instance label, and job-group deletion. | Apache-2.0 | specs | docs |
| rclone | Self-hosted file sync: copy/sync/check semantics (including the corruption failure mode), JSON listings, and rclone serve http. | MIT | specs | docs |
| restic | Self-hosted backup: init → backup → restore round-trip, snapshot JSON, diff, integrity check, retention, wrong-password failure mode. | BSD-2-Clause | specs | docs |
| transfer.sh | Self-hosted file sharing: a PNG uploaded as the raw request body (body_file), downloaded back to disk (body_to), and verified byte-for-byte; multipart uploads too. | MIT | specs | docs |
| webhook | Self-hosted webhook receiver driven by config fixtures: a request runs a command whose file side effect and output are both checked, a value trigger-rule gates execution (and blocks it, proven by the absent side effect), an HMAC signature verified against an openssl-computed oracle, the http-methods allowlist (405), and a non-zero command surfaced as 500. | MIT | specs | docs |