{"id":48640372,"url":"https://github.com/indaco/malt","last_synced_at":"2026-04-23T01:02:41.563Z","repository":{"id":350194821,"uuid":"1205189436","full_name":"indaco/malt","owner":"indaco","description":"A fast, Homebrew-compatible package manager for macOS.","archived":false,"fork":false,"pushed_at":"2026-04-09T09:38:08.000Z","size":2717,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-09T10:25:01.221Z","etag":null,"topics":["brew","cli","homebrew","package-manager","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/indaco.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T18:12:02.000Z","updated_at":"2026-04-09T09:38:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/indaco/malt","commit_stats":null,"previous_names":["indaco/malt"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/indaco/malt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fmalt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fmalt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fmalt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fmalt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/indaco","download_url":"https://codeload.github.com/indaco/malt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indaco%2Fmalt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31851057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brew","cli","homebrew","package-manager","zig"],"created_at":"2026-04-09T18:18:22.838Z","updated_at":"2026-04-23T01:02:41.045Z","avatar_url":"https://github.com/indaco.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# malt\n\n**A Homebrew client in Zig. Warm installs in milliseconds. `post_install` scripts that actually run.**\n\n![macOS only](https://img.shields.io/badge/platform-macOS-blue)\n![Version](https://img.shields.io/github/v/tag/indaco/malt?label=version\u0026sort=semver\u0026color=4c1)\n![Coverage](.github/badges/coverage.svg)\n![Zig 0.16.x](https://img.shields.io/badge/zig-0.16.x-orange)\n![License](https://img.shields.io/badge/license-MIT-green)\n[![Signed by cosign](https://img.shields.io/badge/signed-cosign-brightgreen?logo=sigstore\u0026logoColor=white)](#security)\n[![Built with Devbox](https://www.jetify.com/img/devbox/shield_galaxy.svg)](https://www.jetify.com/devbox/docs/contributor-quickstart/)\n\n\u003e [!NOTE]\n\u003e **Experimental project.** All implementation code in malt was written by AI ([Claude Code](https://claude.ai/code) and [ruflo](https://github.com/ruvnet/ruflo)). The design, architecture, and every merged change were directed and reviewed by a human. It's a hands-on look at how far human + AI pair-programming can go on a non-trivial systems project — and the tool **actually works**.\n\nmalt is a macOS package manager written in Zig that reuses Homebrew's formula, bottle, cask, and tap ecosystem — a client for the registry, not a fork. Single binary, ~3 MB, ~3 ms cold start. Requires macOS 11+ on Apple Silicon or Intel.\n\nUnlike other alternative clients, malt runs Homebrew `post_install` blocks natively via a built-in Zig interpreter, so packages like `node`, `openssl`, `fontconfig`, and `docbook` are fully configured at install time. On warm installs — the common case after day one — malt is the fastest tool measured on packages with dependencies. See [Benchmarks](#benchmarks).\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003e\u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e\u003c/b\u003e \u0026middot;\n  \u003cb\u003e\u003ca href=\"#install\"\u003eInstall\u003c/a\u003e\u003c/b\u003e \u0026middot;\n  \u003cb\u003e\u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e\u003c/b\u003e \u0026middot;\n  \u003cb\u003e\u003ca href=\"#command-reference\"\u003eCommands\u003c/a\u003e\u003c/b\u003e \u0026middot;\n  \u003cb\u003e\u003ca href=\"#post-install-dsl-interpreter\"\u003ePost-Install\u003c/a\u003e\u003c/b\u003e \u0026middot;\n  \u003cb\u003e\u003ca href=\"#benchmarks\"\u003eBenchmarks\u003c/a\u003e\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/indaco/gh-assets/main/malt/demo.gif\" alt=\"malt install jq tree ripgrep — demo\" width=\"800\"\u003e\n\u003c/p\u003e\n\n---\n\n## Features\n\n- **Native post_install execution** — a built-in Zig interpreter runs Homebrew `post_install` scripts that every other tool skips, so packages actually work after install\n- **System Ruby fallback** — `--use-system-ruby` delegates to any installed Ruby for the handful of scripts the interpreter doesn't cover\n- **Isolated** — installs to its own prefix, never touches Homebrew's files\n- **Deduplicated storage** — identical files across versions are stored only once\n- **Parallel downloads** — fetches multiple packages at the same time\n- **Brew fallback** — hands off to Homebrew for anything it doesn't support\n- **Rollback** — revert to a previous version of any package\n- **Ephemeral run** — `malt run` launches a formula without installing it permanently\n- **Services** — `malt services` manages long-running launchd processes, `brew services`-compatible\n- **Bundles** — `malt bundle install` reads existing `Brewfile`s with no conversion\n- **Polished output** — readable by default, scriptable via `--json`\n- **Path-sandboxed execution** — the DSL interpreter validates all writes stay within the package prefix\n- **Safe under concurrency** — multiple malt processes won't corrupt state\n\n---\n\n## Install\n\n### One-liner (recommended)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/indaco/malt/main/scripts/install.sh | bash\n```\n\nDownloads the latest release, verifies the SHA256 checksum **and a cosign keyless signature**, installs the binary to `/usr/local/bin/`, and creates `/opt/malt` with proper ownership. Falls back to building from source if no release is available.\n\n\u003e [!NOTE]\n\u003e The installer requires [`cosign`](https://docs.sigstore.dev/cosign/system_config/installation/) on your `PATH` to cryptographically verify that the release was produced by malt's GitHub Actions workflow. To bypass (not recommended), set `MALT_ALLOW_UNVERIFIED=1`.\n\n#### Verifying `install.sh` itself\n\nThe pipe-to-bash pattern can't verify the script that's fetching it. To verify `install.sh` out of band, check it against the tagged copy in this repository:\n\n```bash\n# Pin to a release tag (recommended):\ncurl -fsSL \"https://raw.githubusercontent.com/indaco/malt/v0.5.1/scripts/install.sh\" -o install.sh\nshasum -a 256 install.sh\n# Compare against the SHA printed in the release notes for v0.5.1, then:\nbash install.sh\n```\n\nOnce `install.sh` runs, it re-verifies every subsequent download with cosign, so the trust anchor shifts from \"whatever HTTPS returned\" to \"the Sigstore-pinned workflow identity\".\n\n### Via Homebrew\n\nMalt is published as a cask under [`indaco/homebrew-tap`](https://github.com/indaco/homebrew-tap):\n\n```bash\nbrew install --cask indaco/tap/malt\n```\n\nThe qualified `\u003ctap\u003e/\u003ccask\u003e` shorthand taps implicitly, so no separate `brew tap` step is needed. Upgrade with `brew upgrade --cask malt`. `mt version update` detects Homebrew installs and defers to brew automatically — updating underneath brew would break its install receipt.\n\n### From source\n\nClone the repo and run the install script — it detects the local checkout and builds from source automatically:\n\n```bash\ngit clone https://github.com/indaco/malt.git\ncd malt\n./scripts/install.sh\n```\n\nRequires [Zig 0.16.x](https://ziglang.org/download/).\n\n\u003e [!NOTE]\n\u003e `zig build` produces both `malt` and `mt` in `zig-out/bin/`. Both are identical — use whichever you prefer. All install methods (script, Homebrew, source) install both.\n\n---\n\n## Quick Start\n\n\u003e [!TIP]\n\u003e `mt` is a built-in alias for `malt`. Every command works with either name — use `mt` if you prefer fewer keystrokes.\n\u003e\n\u003e Additional aliases: `remove` for `uninstall`, `ls` for `list`.\n\n```bash\n# Install a formula (resolves dependencies automatically)\nmalt install wget\n\n# Install a cask (auto-detected)\nmalt install --cask firefox\n\n# Install from a tap (inline — no separate tap step)\nmalt install user/tap/formula\n\n# Install from a local .rb file (explicit opt-in)\nmalt install --local ./wget.rb\n\n# Install multiple packages (downloads in parallel)\nmalt install jq wget ripgrep\n\n# List installed packages\nmalt list --versions\n\n# Uninstall\nmalt uninstall wget\n```\n\n---\n\n## Command Reference\n\n\u003e [!NOTE]\n\u003e The examples below use `mt` (the shorter alias). All commands work identically with `malt`.\n\u003e\n\u003e **Plural flag forms.** Wherever a command accepts `--formula` or `--cask`, it also accepts `--formulae` or `--casks` — whichever reads more naturally for the number of packages involved.\n\n### `mt install`\n\nInstall formulas, casks, or tap formulas.\n\n```bash\nmt install \u003cpackage\u003e                     # auto-detect formula or cask\nmt install \u003cpackage\u003e@\u003cversion\u003e           # versioned formula (e.g. openssl@3)\nmt install --cask \u003capp\u003e                  # explicit cask\nmt install --formula \u003cname\u003e              # explicit formula\nmt install \u003cuser\u003e/\u003ctap\u003e/\u003cformula\u003e        # inline tap (no separate tap step)\nmt install --local \u003cpath.rb\u003e             # install from a local Ruby formula\nmt install \u003cpackage\u003e [\u003cpackage\u003e ...]     # multiple packages\n```\n\n| Flag                           | Description                                                          |\n| ------------------------------ | -------------------------------------------------------------------- |\n| `--cask`                       | Force cask installation                                              |\n| `--formula`                    | Force formula installation                                           |\n| `--local`                      | Install from a local `.rb` path (code-exec surface — trust required) |\n| `--dry-run`                    | Show what would be installed without installing                      |\n| `--force`                      | Overwrite existing installations                                     |\n| `--use-system-ruby[=\u003cname\u003e,…]` | Run `post_install` via system Ruby (sandboxed, per-formula)          |\n| `--quiet`, `-q`                | Suppress all output except errors                                    |\n| `--json`                       | Output result as JSON                                                |\n\n\u003e [!IMPORTANT]\n\u003e **`--local` trust boundary.** Installing from a `.rb` path trusts that file to name the archive URL and SHA256 of what ends up on your system. Use it for your own formulas, for experimenting with upstream changes before they land in a tap, or for private in-house packages — never for a `.rb` you did not read.\n\u003e\n\u003e malt prints the canonical realpath on every install so an attentive reader notices surprises like `/tmp/...`. A leading `./`, `/`, `~/`, or any embedded slash combined with a `.rb` suffix is auto-detected as a local path; the same warning fires either way. Bare filenames (e.g. `wget.rb` without `./`) are _not_ auto-detected — pass `--local` to disambiguate.\n\u003e\n\u003e **Security guardrails.** The archive URL inside the `.rb` must be `https://` — plaintext HTTP, `file://`, `ftp://`, and `data:` are rejected before any download. The SHA256 check uses a constant-time compare. An extra `⚠` line fires if the `.rb` is world-writable or owned by a different user. Combining `--local` with `--cask`, `--formula`, or `--use-system-ruby` is refused up front.\n\u003e\n\u003e **Not supported for local installs.** `depends_on` and `post_install` blocks inside the `.rb` are **not** evaluated — only the bottle-style `version` + `url` + `sha256` triple (optionally inside `on_macos` / `on_arm` / `on_intel`). If you need dependencies or `post_install` behaviour, publish the formula to a tap and install via `mt install user/tap/formula`.\n\u003e\n\u003e **Archive formats.** The `url` must end in `.tar.gz` / `.tgz`, `.tar.xz`, or `.zip`. Anything else is rejected with a clear error before download.\n\u003e\n\u003e **Minimal compatible `.rb`.** This is the full shape malt reads — everything else is ignored:\n\u003e\n\u003e ```ruby\n\u003e class Hello \u003c Formula\n\u003e   version \"1.2.3\"\n\u003e   on_macos do\n\u003e     on_arm do\n\u003e       url \"https://example.com/hello-#{version}-arm64.tar.gz\"\n\u003e       sha256 \"aaaa…\"   # 64 hex chars\n\u003e     end\n\u003e     on_intel do\n\u003e       url \"https://example.com/hello-#{version}-x86_64.tar.gz\"\n\u003e       sha256 \"bbbb…\"\n\u003e     end\n\u003e   end\n\u003e end\n\u003e ```\n\u003e\n\u003e The formula name comes from the `.rb` basename (so `hello.rb` installs `hello`). A flat `url` / `sha256` at the top level works for single-arch archives. See `scripts/fixtures/local_formulae/hello.rb` for a runnable example.\n\n\u003e [!NOTE]\n\u003e **Post-install scripts run natively.** malt includes a built-in interpreter that executes Homebrew `post_install` blocks in Zig — no Ruby required. Packages like `node`, `openssl`, `fontconfig`, and `docbook` are fully configured at install time. For the small number of scripts the interpreter doesn't cover, add `--use-system-ruby` to delegate to any available Ruby, or use `brew install` as a fallback. See [Post-Install DSL Interpreter](#post-install-dsl-interpreter) for details.\n\n\u003e [!IMPORTANT]\n\u003e `--use-system-ruby` is **per-formula**. The bare flag works only when installing a single package (`mt install jq --use-system-ruby`). Multi-package installs must scope it: `mt install jq wget --use-system-ruby=jq` enables Ruby for `jq` only. `mt migrate` rejects the bare form entirely. This keeps one package's `post_install` failing from silently widening the trust boundary to the rest of the batch. The Ruby subprocess itself runs sandboxed — see [Security](#security).\n\n### `mt uninstall`\n\nRemove installed packages.\n\n```bash\nmt uninstall \u003cpackage\u003e\nmt uninstall --cask \u003capp\u003e\nmt uninstall \u003cpackage\u003e --force           # ignore dependents check\n```\n\n| Flag            | Description                                |\n| --------------- | ------------------------------------------ |\n| `--force`, `-f` | Remove even if other packages depend on it |\n| `--cask`        | Force cask uninstall                       |\n\nChecks for dependent packages before removing. If dependents exist, refuses unless `--force` is passed. For casks, checks if the application is running and refuses unless `--force` is passed. Store entries are preserved for `mt purge --store-orphans`.\n\n### `mt upgrade`\n\nUpgrade installed packages to latest versions.\n\n```bash\nmt upgrade \u003cpackage\u003e                     # upgrade a specific formula or cask\nmt upgrade --cask                        # upgrade all outdated casks\nmt upgrade --formula                     # upgrade all outdated formulas\nmt upgrade --dry-run                     # show what would be upgraded\n```\n\n| Flag        | Description               |\n| ----------- | ------------------------- |\n| `--cask`    | Upgrade casks only        |\n| `--formula` | Upgrade formulas only     |\n| `--dry-run` | Preview without upgrading |\n\nFormula upgrades install the new version, verify it, switch symlinks atomically, and only remove the old version after success. On failure, the old version is restored automatically.\n\n### `mt update`\n\nRefresh the local formula/cask metadata cache.\n\n```bash\nmt update\n```\n\nInvalidates all entries in the API cache. The next `install`, `search`, or `info` command fetches fresh data from the Homebrew API.\n\n### `mt outdated`\n\nList packages with newer versions available.\n\n```bash\nmt outdated\nmt outdated --json\nmt outdated --cask\nmt outdated --formula\n```\n\n| Flag            | Description                 |\n| --------------- | --------------------------- |\n| `--json`        | Output as JSON              |\n| `--formula`     | Show outdated formulas only |\n| `--cask`        | Show outdated casks only    |\n| `--quiet`, `-q` | Suppress status messages    |\n\nCompares installed versions against the latest from the Homebrew API. Checks both formulas and casks by default.\n\n```text\nwget (1.24.5) \u003c 1.25.0\nopenssl@3 (3.3.2) \u003c 3.4.1\n```\n\n### `mt list`\n\nList installed packages.\n\n```bash\nmt list\nmt list --versions\nmt list --cask\nmt list --formula\nmt list --pinned\nmt list --json\n```\n\n### `mt info`\n\nShow detailed information about a formula or cask.\n\n```bash\nmt info \u003cpackage\u003e\nmt info \u003cpackage\u003e --json\nmt info --cask \u003capp\u003e\nmt info --formula \u003cname\u003e\n```\n\n| Flag        | Description            |\n| ----------- | ---------------------- |\n| `--formula` | Show formula info only |\n| `--cask`    | Show cask info only    |\n| `--json`    | Output as JSON         |\n\nAuto-detects whether the package is a formula or cask. For formulas, shows version, tap, cellar path, and pinned status. For casks, shows version, download URL, app path, and auto-update status.\n\n### `mt search`\n\nSearch formulas and casks by name.\n\n```bash\nmt search \u003cquery\u003e\nmt search \u003cquery\u003e --formula\nmt search \u003cquery\u003e --cask\nmt search \u003cquery\u003e --json\n```\n\n### `mt uses`\n\nShow installed packages that depend on a given formula. Default is direct dependents only; `--recursive` walks the transitive closure.\n\n```bash\nmt uses \u003cformula\u003e\nmt uses --recursive \u003cformula\u003e\nmt uses \u003cformula\u003e --json\n```\n\n### `mt doctor`\n\nSystem health check.\n\n```bash\nmt doctor\nmt doctor --post-install-status   # check DSL support per installed formula\n```\n\n| Check               | Pass                                          | Fail                                     |\n| ------------------- | --------------------------------------------- | ---------------------------------------- |\n| SQLite integrity    | `PRAGMA integrity_check` returns `ok`         | Error: database corrupt                  |\n| Directory structure | All required directories exist under prefix   | Warn: missing directory                  |\n| Stale lock          | No lock file, or lock PID is running          | Warn: suggest removal                    |\n| APFS volume         | `/opt/malt` is on APFS                        | Warn: clonefile unavailable              |\n| API reachable       | HEAD to `formulae.brew.sh` returns 2xx        | Warn: offline                            |\n| Orphaned store      | All store entries referenced by a keg         | Warn: suggest `mt purge --store-orphans` |\n| Missing kegs        | All DB keg paths exist on disk                | Error: suggest reinstall                 |\n| Broken symlinks     | All symlinks in bin/, lib/ etc. resolve       | Warn: suggest `mt purge --housekeeping`  |\n| Disk space          | \u003e 1 GB free on prefix volume                  | Warn: low disk space                     |\n| Post-install DSL    | All installed post_install formulae parseable | Warn: unsupported construct              |\n\nExits with code 0 (all OK), 1 (warnings found), or 2 (errors found).\n\n### `mt purge`\n\nUnified housekeeping and full-wipe command. A scope flag selects what to remove — `mt purge` with no scope is an error.\n\n```bash\n# Housekeeping\nmt purge --store-orphans                 # refcount-0 store blobs (was: mt gc)\nmt purge --unused-deps                   # orphaned dep kegs    (was: mt autoremove)\nmt purge --cache=30                      # cache files older than N days\nmt purge --stale-casks                   # cache + Caskroom for uninstalled casks\nmt purge --housekeeping                  # all four safe scopes at once\n\n# Destructive (typed-confirm unless --yes)\nmt purge --downloads                     # wipe {cache}/downloads entirely\nmt purge --old-versions                  # remove non-latest Cellar versions\nmt purge --wipe                          # nuclear: every malt artefact on disk\n\n# Combine, preview, gate\nmt purge --store-orphans --cache=7 --dry-run\nmt purge --wipe --backup ~/snapshot.txt --remove-binary --yes\n```\n\n| Scope             | Removes                                                 | Confirm gate        |\n| ----------------- | ------------------------------------------------------- | ------------------- |\n| `--store-orphans` | Refcount-0 blobs in `{prefix}/store`                    | none                |\n| `--unused-deps`   | Indirect-install kegs no other package needs            | none                |\n| `--cache[=DAYS]`  | Cache files older than DAYS (default 30)                | none                |\n| `--downloads`     | Entire `{cache}/downloads` directory                    | type `downloads`    |\n| `--stale-casks`   | Cask cache + Caskroom entries for uninstalled casks     | none                |\n| `--old-versions`  | Non-latest version directories in `{prefix}/Cellar`     | type `old-versions` |\n| `--housekeeping`  | = `--store-orphans --unused-deps --cache --stale-casks` | none                |\n| `--wipe`          | Every malt artefact on disk (mutually exclusive)        | type `purge`        |\n\n| Shared flag             | Description                                                      |\n| ----------------------- | ---------------------------------------------------------------- |\n| `--dry-run`, `-n`       | Preview every removal without touching disk                      |\n| `--yes`, `-y`           | Skip every typed-confirmation prompt                             |\n| `--quiet`, `-q`         | Suppress per-item output                                         |\n| `--backup`, `-b` _path_ | Write a `mt restore`-compatible manifest **before** any deletion |\n\n| `--wipe`-only flag | Description                                                                  |\n| ------------------ | ---------------------------------------------------------------------------- |\n| `--keep-cache`     | Preserve the cache directory (downloaded bottles stay on disk for reinstall) |\n| `--remove-binary`  | Also unlink `/usr/local/bin/{mt,malt}` (opt-in — they live outside prefix)   |\n\n`--wipe` cannot be combined with any other scope flag — it already supersedes them. For everything except `--wipe`, multiple scopes can be passed in a single invocation and run sequentially under one lock acquisition.\n\nAcquires `{prefix}/db/malt.lock` before any destructive scope runs so concurrent malt processes cannot race; for `--wipe`, the lock is released before removing the `db/` directory itself. Honours `MALT_PREFIX` and `MALT_CACHE`, so pointing those at a throwaway path is the safe way to test the command end-to-end.\n\nUse `mt uninstall \u003cname\u003e` for per-package removal — `mt purge` deals exclusively with housekeeping artefacts and full uninstalls.\n\n### `mt tap` / `mt untap`\n\nManage taps explicitly. Taps are auto-resolved during install, so this is optional.\n\n```bash\nmt tap \u003cuser\u003e/\u003crepo\u003e                    # register a tap\nmt tap                                  # list registered taps\nmt untap \u003cuser\u003e/\u003crepo\u003e                  # remove a tap\n```\n\n### `mt migrate`\n\nImport an existing Homebrew installation.\n\n```bash\nmt migrate\nmt migrate --dry-run\n```\n\nScans the Homebrew Cellar, resolves each installed package via the API, and installs it through malt. Does **not** modify the Homebrew installation. Packages with `post_install` hooks are executed via the native DSL interpreter; unsupported scripts fall back to `--use-system-ruby` or are skipped with a report.\n\n### `mt backup`\n\nDump the list of directly-installed formulas and casks to a plain-text file for later restoration on the same or another machine.\n\n```bash\nmt backup                                # writes malt-backup-\u003ctimestamp\u003e.txt to cwd\nmt backup --output my-setup.txt          # custom path\nmt backup -o -                           # write to stdout\nmt backup --versions                     # pin each entry to its installed version\n```\n\n| Flag             | Description                                         |\n| ---------------- | --------------------------------------------------- |\n| `--output`, `-o` | Destination path (`-` for stdout)                   |\n| `--versions`     | Append `@\u003cversion\u003e` to each entry for exact pinning |\n| `--quiet`, `-q`  | Suppress status messages                            |\n\nOnly directly-installed formulas are recorded — transitive dependencies are resolved again on restore. The file format is plain text, one entry per line (`formula \u003cname\u003e` or `cask \u003ctoken\u003e`), with `#` comments. It is safe to hand-edit before restoring.\n\n### `mt restore`\n\nReinstall every entry in a backup file produced by `mt backup`.\n\n```bash\nmt restore my-setup.txt\nmt restore my-setup.txt --dry-run        # preview what would be installed\nmt restore my-setup.txt --force          # pass --force to the underlying installs\n```\n\n| Flag            | Description                                        |\n| --------------- | -------------------------------------------------- |\n| `--dry-run`     | Print the list of packages without installing      |\n| `--force`       | Forward `--force` to `mt install` for each package |\n| `--quiet`, `-q` | Suppress status messages                           |\n\nFormulas and casks are batched into two `mt install` invocations, so dependency resolution, parallel downloads, and the atomic install protocol all apply. Lines prefixed with `#` and blank lines are ignored, and entries with a `@\u003cversion\u003e` suffix are installed at that exact version.\n\n### `mt services`\n\nManage long-running background processes via launchd. Equivalent to `brew services`.\n\n```bash\nmt services list                         # show registered services + runtime state\nmt services start postgresql@16          # bootstrap into the user launchd domain\nmt services stop  postgresql@16\nmt services restart postgresql@16\nmt services status postgresql@16         # combined DB + launchctl state\nmt services logs postgresql@16 --tail 50 # last 50 lines of stdout\nmt services logs postgresql@16 --stderr  # read stderr instead\n```\n\n| Subcommand | Description                                                                |\n| ---------- | -------------------------------------------------------------------------- |\n| `list`     | Show every registered service with `running` / `loaded` / `stopped`        |\n| `start`    | Generate and `launchctl bootstrap` the service into `gui/\u003cuid\u003e`            |\n| `stop`     | `launchctl bootout` the service                                            |\n| `restart`  | `stop` then `start`                                                        |\n| `status`   | Combined DB record + live `launchctl list` state                           |\n| `logs`     | Tail `stdout.log` (or `stderr.log` with `--stderr`); `--tail N` sets count |\n\nServices are registered automatically when an installed formula carries a `service` block (e.g. `postgresql@16`, `redis`). State lives at `{prefix}/var/malt/services/\u003cname\u003e/` (plist + log files) and in the SQLite `services` table. Currently macOS-only — Linux/Windows return `OsNotSupported`.\n\n### `mt bundle`\n\nGroup-install and export sets of packages. Drop-in for `brew bundle`: reads existing `Brewfile`s with no conversion.\n\n```bash\nmt bundle install                            # ./Brewfile or ./Maltfile.json\nmt bundle install path/to/Brewfile           # explicit file\nmt bundle install --dry-run                  # print what would happen\nmt bundle create                             # snapshot installed -\u003e ./Brewfile\nmt bundle create --format json my.json       # JSON output\nmt bundle export                             # print current install to stdout\nmt bundle export --format json my-bundle     # named bundle, JSON\nmt bundle list                               # registered bundles\nmt bundle remove devtools                    # unregister (does not uninstall)\nmt bundle import path/to/Brewfile            # register without installing\n```\n\n| Subcommand | Description                                                                |\n| ---------- | -------------------------------------------------------------------------- |\n| `install`  | Install every member; idempotent — already-installed members are skipped   |\n| `create`   | Write the currently-installed set to a bundle file                         |\n| `list`     | List bundles registered in the database                                    |\n| `remove`   | Unregister a bundle (use `--purge` to also uninstall its members)          |\n| `export`   | Print bundle (or current install) to stdout in `brewfile` or `json` format |\n| `import`   | Register a bundle definition without installing                            |\n\n| Flag               | Description                                           |\n| ------------------ | ----------------------------------------------------- |\n| `--dry-run`        | Print every member action without forking             |\n| `--format \u003cfmt\u003e`   | `brewfile` (default) or `json`                        |\n| `--from-installed` | (`create`) populate from currently-installed packages |\n| `--purge`          | (`remove`) also uninstall each member                 |\n\n**Bundlefile lookup order** (when no path is given): `./Brewfile` -\u003e `./Maltfile.json` -\u003e `~/.config/malt/Brewfile` -\u003e `~/.config/malt/Maltfile.json`.\n\n**Brewfile compatibility**: malt parses the standard directive set (`tap`, `brew`, `cask`, `mas`, `vscode`) including hash options (`version:`, `restart_service:`, `link:`) and Ruby symbols (`restart_service: :changed`). Conditionals (`if OS.mac?`) and `do … end` blocks are rejected with a clear error pointing to `Maltfile.json` for power-user cases.\n\n### `mt rollback`\n\nRevert a formula to its previous version using the content-addressable store.\n\n```bash\nmt rollback \u003cpackage\u003e\nmt rollback \u003cpackage\u003e --dry-run\n```\n\nThe store retains all previously installed bottle versions. Rollback unlinks the current version, materializes the previous one from the store, and updates the database. No re-download needed.\n\n### `mt run`\n\nRun a package binary without installing it.\n\n```bash\nmt run \u003cpackage\u003e -- \u003cargs...\u003e\nmt run jq -- --version\nmt run ripgrep -- --help\n```\n\nDownloads the bottle to a temp directory, extracts the binary, executes it with the provided arguments, and cleans up. If the package is already installed, runs the installed binary directly.\n\n### `mt link` / `mt unlink`\n\nManage symlinks for installed kegs.\n\n```bash\nmt link \u003cformula\u003e                        # create prefix symlinks for a keg\nmt link \u003cformula\u003e --overwrite            # replace conflicting symlinks\nmt unlink \u003cformula\u003e                      # remove symlinks (keg stays installed)\n```\n\n| Flag                           | Description               |\n| ------------------------------ | ------------------------- |\n| `--overwrite`, `--force`, `-f` | Replace existing symlinks |\n\n`link` scans for symlink conflicts before creating links. If conflicts are found, it reports them and aborts unless `--overwrite` is passed. `unlink` removes symlinks from `bin/`, `lib/`, etc. and the `opt/` symlink, but leaves the keg installed in the Cellar.\n\n### `mt version`\n\nShow the current version or self-update the binary.\n\n```bash\nmt version                           # show current version\nmt version update                    # install latest (interactive confirm)\nmt version update --check            # check only, no download\nmt version update --yes              # non-interactive (CI / scripts)\nmt version update --cleanup          # remove stale .old + orphaned staging files\n```\n\n`update` queries the GitHub releases API, verifies the release with cosign and SHA256 against the same trust anchor as `install.sh`, and atomically replaces the running binary. The previous binary is preserved at `\u003ctarget\u003e.old` for manual rollback; accumulated `.old` files (and any orphaned `.malt-update-\u003cpid\u003e` staging files from killed updates) can be removed with `mt version update --cleanup`, which runs locally and makes no network calls.\n\n**Homebrew installs.** If malt was installed via `brew install --cask indaco/tap/malt`, the updater detects this and prints a hint pointing you at `brew upgrade --cask malt` — overwriting a brew-managed file from underneath would corrupt its install receipt and break the next `brew upgrade`.\n\n**Verification.** Every update downloads `checksums.txt` and its Sigstore bundle, runs `cosign verify-blob` pinned to the release workflow's OIDC identity, then confirms the tarball's SHA256 against the verified checksums file. [`cosign`](https://docs.sigstore.dev/cosign/system_config/installation/) must be on your `PATH`; without it, `mt version update` refuses rather than silently skip the check. To bypass (strongly discouraged):\n\n```bash\nMALT_ALLOW_UNVERIFIED=1 mt version update --no-verify\n```\n\n\u003e [!NOTE]\n\u003e `install.sh` accepts `MALT_ALLOW_UNVERIFIED=1` alone as the bypass; `mt version update` requires both the env var and `--no-verify`. Update is the command that runs repeatedly — the extra guard is deliberate.\n\n### `mt completions`\n\nGenerate a shell completion script for `bash`, `zsh`, or `fish`. The script is printed to stdout, so it can be eval'd immediately or redirected to a file for permanent install.\n\n```bash\n# Temporary (current shell only)\neval \"$(malt completions bash)\"\neval \"$(malt completions zsh)\"       # run AFTER `compinit`\nmalt completions fish | source\n\n# Permanent\nmalt completions bash \u003e /usr/local/etc/bash_completion.d/malt\nmalt completions zsh  \u003e \"${fpath[1]}/_malt\"\nmalt completions fish \u003e ~/.config/fish/completions/malt.fish\n```\n\nCompletes subcommands (for both `malt` and `mt`), per-command flags, global flags, and the positional shell name for `completions` itself. Unknown shell names exit non-zero with an error.\n\n### Global Flags\n\n| Flag              | Description                                                                     |\n| ----------------- | ------------------------------------------------------------------------------- |\n| `--verbose`, `-v` | Verbose output (all commands)                                                   |\n| `--debug`         | Surface every DSL diagnostic (implies verbose); pair with issue reports         |\n| `--quiet`, `-q`   | Suppress non-error output (all commands)                                        |\n| `--json`          | JSON output (read commands; also emits per-package `post_install` status lines) |\n| `--dry-run`       | Preview without executing (mutating commands)                                   |\n| `--help`, `-h`    | Show help                                                                       |\n| `--version`       | Show version                                                                    |\n\n---\n\n## How It Works\n\n### Directory Layout\n\nmalt installs to `/opt/malt` — its own prefix, fully isolated from Homebrew. The shorter path guarantees that Mach-O load command patching always has room to replace the original Homebrew path.\n\n```text\n/opt/malt/\n├── store/          # Content-addressable bottle storage (immutable, by SHA256)\n├── Cellar/         # Installed kegs (APFS cloned from store/)\n├── Caskroom/       # Installed cask applications\n├── opt/            # Versioned formula symlinks\n├── bin/            # Symlinks to keg binaries\n├── lib/            # Symlinks to keg libraries\n├── include/        # Symlinks to keg headers\n├── share/          # Symlinks to keg shared data\n├── tmp/            # In-progress downloads and extractions\n├── cache/          # Cached API responses (TTL-based)\n└── db/             # SQLite database + advisory lock\n```\n\n### Content-Addressable Store\n\nBottles are stored by their SHA256 hash. The same bottle is never downloaded or extracted twice. Multiple installed kegs can reference the same store entry. Store entries are immutable — only `mt purge --store-orphans` removes them.\n\nKegs in `Cellar/` are materialized from `store/` via APFS `clonefile()`, which creates a copy-on-write clone at zero disk cost. On non-APFS volumes, a regular recursive copy is used as fallback.\n\n### Download Pipeline\n\nEach bottle download is a single-pass pipeline:\n\n```text\nNetwork (HTTPS from GHCR CDN)\n    ├──-\u003e SHA256 hasher (streaming — computed as chunks arrive)\n    └──-\u003e gzip/zstd decompressor\n            └──-\u003e tar extractor\n                    └──-\u003e filesystem write to tmp/\n```\n\nNo intermediate archive file is written to disk. The SHA256 is verified against the Homebrew API manifest immediately after the stream completes. On mismatch, the extracted directory is deleted.\n\n### Mach-O Binary Patching\n\nHomebrew bottles contain hardcoded paths like `/opt/homebrew/Cellar/...` in Mach-O load commands. Since malt uses its own prefix, these paths must be rewritten.\n\nmalt parses Mach-O headers using struct-aware parsing (not raw byte scanning), identifies all relevant load commands (`LC_ID_DYLIB`, `LC_LOAD_DYLIB`, `LC_RPATH`, etc.), and replaces paths in-place, padding the remaining space with null bytes. On arm64, every patched binary is ad-hoc codesigned via `codesign --force --sign -`.\n\nText files (`.pc` configs, shell scripts) containing `@@HOMEBREW_PREFIX@@` or `@@HOMEBREW_CELLAR@@` placeholders are also patched.\n\nPatching is always performed on the Cellar copy, never the store original. If patching fails, the Cellar copy is deleted and the store entry remains pristine for retry.\n\n### Post-Install DSL Interpreter\n\nSome packages aren't really installed the moment their files hit disk. They ship a `post_install` block that creates symlinks, sets up man pages, generates caches, or writes config files the binary expects to find at runtime. Skip that step and the package extracts fine but isn't usable. malt runs these scripts natively — no Ruby runtime required.\n\nmalt includes a native Zig interpreter for the Ruby subset used in `post_install` blocks. It adds under 80 KB to the binary and activates only for formulae that define the method.\n\n- **Parses and evaluates** Ruby source in a sandboxed context bound to the formula's Cellar and prefix paths\n- **Covers** Pathname ops, FileUtils, string interpolation, `inreplace`, `Dir.glob`, `if`/`unless`, `.each`/`.select`/`.map`, `Formula[\"name\"]` cross-lookup, `ENV` access, `%w[]` arrays, `\u0026\u0026`/`||`/`!`, and more\n- **Enforces write boundaries** — any filesystem mutation targeting a path outside the formula's Cellar prefix or the malt prefix is rejected outright\n- **Falls back cleanly** — unsupported constructs are logged and the user is directed to `--use-system-ruby`\n- **Fetches source on demand** — if the homebrew-core tap isn't cloned locally, the `.rb` source is fetched directly from GitHub\n\n```text\nExecution cascade:\n\n  Formula has post_install?\n    |\n    yes --\u003e Try native DSL interpreter\n    |         |\n    |         success --\u003e done (package fully configured)\n    |         |\n    |         unsupported construct --\u003e --use-system-ruby set?\n    |                                     |\n    |                                     yes --\u003e delegate to Ruby subprocess\n    |                                     no  --\u003e skip with clear message\n    |\n    no --\u003e done (no post_install needed)\n```\n\n### Atomic Install Protocol\n\nEvery install follows a strict 9-step protocol. Failure at any step triggers cleanup of that step only — no prior state is modified.\n\n1. **Acquire lock** — exclusive advisory lock on `db/malt.lock`\n2. **Pre-flight** — resolve dependencies, check disk space, detect link conflicts\n3. **Download** — fetch bottles from GHCR CDN with streaming SHA256 verification\n4. **Extract** — decompress and untar to `tmp/`\n5. **Commit to store** — atomic rename from `tmp/` to `store/`\n6. **Materialize** — APFS clonefile from `store/` to `Cellar/`, patch Mach-O, codesign\n7. **Link** — create symlinks in `bin/`, `lib/`, etc., record in DB\n8. **DB commit** — insert into kegs, dependencies, links tables in a single transaction\n9. **Release lock** — clean up tmp files\n\n---\n\n## Safety Guarantees\n\n- **SHA256 verification** — streaming hash computed during download, verified before extraction. No unverified data touches the store.\n- **Pre-flight checks** — dependencies resolved, disk space verified, and link conflicts detected before any download begins.\n- **Link conflict detection** — all target symlink paths scanned before creating any links. Conflicts abort the operation with a clear report.\n- **Atomic installs** — the 9-step protocol uses `errdefer` at every stage. Interrupted installs leave no partial state.\n- **Concurrent access** — an advisory file lock with a 30-second timeout prevents concurrent mutations. Read-only commands (`list`, `info`, `search`) do not acquire the lock.\n- **Upgrade rollback** — new version is fully installed and verified before the old version is touched. On failure, old symlinks are restored.\n- **Store immutability** — store entries are never modified after commit. Patching happens on the Cellar clone. Only `mt purge --store-orphans` deletes store entries.\n- **DSL path sandboxing** — the post_install interpreter validates every mutating filesystem operation (write, rm, chmod, symlink) against the formula's Cellar prefix and the malt prefix. Paths containing `..` or resolving outside the sandbox via symlinks are rejected immediately.\n\n\u003e [!TIP]\n\u003e See [Security](#security) for the supply-chain and sandboxing properties — release signing, pinned third-party sources, and the `sandbox-exec` profile around the opt-in Ruby `post_install` path.\n\n---\n\n## Security\n\n- **Signed releases.** Every release is cosign-signed keyless via GitHub OIDC; `install.sh` verifies the signature before trusting the SHA256 checksum. A leaked GitHub token isn't enough to ship a malicious malt binary.\n- **Pinned third-party source.** `homebrew-core` and third-party taps are pinned to a specific commit SHA. Formula Ruby source is SHA256-verified against an embedded manifest at that commit. A rewritten upstream branch cannot substitute a formula's bottle URL mid-install. Advance a tap pin explicitly with `mt tap --refresh user/repo`.\n- **Sandboxed `post_install`.** The opt-in `--use-system-ruby` path runs inside a `sandbox-exec` profile scoped to the formula's cellar, with a scrubbed environment, `RLIMIT_CPU`/`AS`/`FSIZE` caps, and terminal escape sequences filtered from child output. A hostile formula's blast radius shrinks to its own install prefix.\n- **Boundary validation.** `MALT_PREFIX`, launchd service declarations, install-script checksums, and HTTP redirects all fail-closed on malformed or suspicious input — no silent HTTPS→HTTP downgrades, no `/bin/sh` in service argv, no `..` in prefix paths.\n- **Posture visibility.** `mt doctor` flags world- or group-writable paths and unexpected ownership under `/opt/malt`, so multi-user machines can see their attack surface at a glance.\n\n---\n\n## Environment Variables\n\n| Variable                       | Description                                                                       | Default          |\n| ------------------------------ | --------------------------------------------------------------------------------- | ---------------- |\n| `MALT_PREFIX`                  | Override install prefix                                                           | `/opt/malt`      |\n| `MALT_CACHE`                   | Override cache directory                                                          | `{prefix}/cache` |\n| `NO_COLOR`                     | Disable colored output                                                            | unset            |\n| `MALT_NO_EMOJI`                | Disable emoji in output                                                           | unset            |\n| `MALT_THEME`                   | Force the output palette: `light`, `dark`, or `auto` (detects via OSC 11)         | `auto`           |\n| `HOMEBREW_GITHUB_API_TOKEN`    | GitHub token for higher API rate limits                                           | unset            |\n| `MALT_ALLOW_UNVERIFIED`        | Skip cosign signature check in `install.sh` (use only when cosign is unavailable) | unset            |\n| `MALT_ALLOW_UNVERIFIED_SOURCE` | Allow `install.sh` to clone `main` when no release tag resolves                   | unset            |\n| `MALT_ALLOW_RAW_POST_INSTALL`  | Disable terminal escape filter on ruby `post_install` output                      | unset            |\n\n---\n\n## Transparent Fallback\n\nFor commands not implemented by malt, malt checks if `brew` is installed and silently delegates the command to it.\n\nIf `brew` is not found, malt prints:\n\n```text\nmalt: '\u003ccmd\u003e' is not a malt command and brew was not found.\nInstall Homebrew: https://brew.sh\n```\n\n---\n\n## Building\n\n```bash\n# Requires Zig 0.16.x\nzig build                                # debug build\nzig build -Doptimize=ReleaseSafe         # release build (~3 MB)\nzig build test                           # run tests\nzig build universal                      # universal binary (arm64 + x86_64 via lipo)\n```\n\n---\n\n## Benchmarks\n\nInstall times on macOS 14 (Apple Silicon), comparing malt against other Homebrew-compatible package managers.\n\n\u003c!-- BENCH:SIZE:START --\u003e\n### Binary Size\n\n| Tool | Size |\n| ---- | ---- |\n| **malt** | 3.1 MB |\n| nanobrew | 1.7 MB |\n| zerobrew | 8.6 MB |\n\u003c!-- BENCH:SIZE:END --\u003e\n\n\u003c!-- BENCH:COLD:START --\u003e\n### Cold Install (median ±σ)\n\n| Package | malt | nanobrew | zerobrew | Homebrew |\n| ------- | ---- | -------- | -------- | -------- |\n| **tree** (0 deps) | 0.630±0.237s | 0.644±0.029s | 1.232±0.071s | 3.323±0.287s |\n| **wget** (6 deps) | 3.408±0.263s | 5.753±1.347s | 8.484±1.862s | 5.109±1.243s |\n| **ffmpeg** (11 deps) | 3.055±0.468s | 3.779±0.191s | 7.375±0.456s | 17.314±0.814s |\n\u003c!-- BENCH:COLD:END --\u003e\n\n\u003c!-- BENCH:WARM:START --\u003e\n### Warm Install\n\n| Package | malt | nanobrew | zerobrew |\n| ------- | ---- | -------- | -------- |\n| **tree** (0 deps) | 0.007s | 0.014s | 0.226s |\n| **wget** (6 deps) | 0.092s | 2.976s | 1.013s |\n| **ffmpeg** (11 deps) | 0.297s | 1.547s | 3.026s |\n\u003c!-- BENCH:WARM:END --\u003e\n\n\u003e [!IMPORTANT]\n\u003e Benchmarks on Apple Silicon (GitHub Actions macos-14), 2026-04-20. Auto-updated weekly via [benchmark workflow](.github/workflows/benchmark.yml).\n\n### Why warm matters more than cold\n\nEvery package is installed _cold_ exactly once per machine — the first time you type `mt install ffmpeg` on a fresh checkout. Everything after that — upgrades, reinstalls, dev-environment rebuilds (devbox, nix-style), CI cache restores, post-cleanup reinstalls — is a _warm_ install against the existing store. In a realistic developer workflow the ratio is roughly **1 cold : 10+ warm** over a machine's lifetime, so the warm row is where the minutes actually add up.\n\nOn that row, malt is the fastest tool measured across packages with dependencies — the common case (`wget` has 6 deps, `ffmpeg` has 11 — most useful packages do). Warm `tree` (0 deps) is within 1 ms of nanobrew, effectively tied. Cold installs are competitive but represent a one-time cost you pay per package, not an ongoing one.\n\nPut plainly: the number that matters after your first day using malt is the warm row, and on that row malt is the fastest tool measured here.\n\n### Reading the numbers\n\nmalt trades a few ms against correctness and features that the lighter tools skip:\n\n- **SQLite state (~1.5 MB of the 3 MB binary)** — ACID writes, reverse-dep queries (`mt uses openssl@3`), linker conflict detection, atomic rollback. nanobrew uses a flat `state.json`.\n- **Native `post_install` interpreter** — runs Homebrew post-install blocks in Zig, no Ruby subprocess. zerobrew skips post_install entirely; nanobrew regex-scrapes a handful of patterns.\n- **Ad-hoc codesign on arm64 (~15 ms/pkg)** — every Mach-O patched to rewrite `/opt/homebrew` -\u003e `MALT_PREFIX` is re-signed so `dyld` will load it.\n- **Global install lock + pre-link conflict check (~3 ms)** — `flock` on `db/malt.lock` + a symlink-tree walk refusing to overwrite another keg's files.\n\n\u003e [!NOTE]\n\u003e **Methodology.**\n\u003e\n\u003e Each cell is the **median of 5 rounds** (`BENCH_ROUNDS=5`, the default in [`scripts/bench.sh`](scripts/bench.sh)). The median damps single-run jitter — network hiccups, launchd transients, disk cache warm-up — without inflating the table the way a mean over a noisy sample would. Override with `BENCH_ROUNDS=N` if you need a different sample count. Every run also emits per-tool `_min` and `_stddev` keys to `$GITHUB_OUTPUT` and prints them in the local terminal summary, so run-to-run noise is visible alongside the published median.\n\u003e\n\u003e **\"Cold\" definition.** A cold sample here starts from a wiped install prefix for every tool, so the first round exercises the full download -\u003e extract -\u003e link -\u003e db-write path. Some benchmark scripts define \"cold\" as an uninstall/reinstall instead, which keeps the download cache warm; the two definitions can produce different absolute cold numbers for the same tool on the same hardware. The warm row uses the same definition across tools either way.\n\u003e\n\u003e `BENCH_TRUE_COLD=1` wipes each tool's install prefix **and** its bottle download cache before every cold sample, so \"cold\" really means \"no bottle anywhere on disk — full network fetch required.\" malt, nanobrew, and zerobrew all keep their download cache inside their install prefix, so the single prefix wipe takes both. Homebrew's cache lives outside the prefix (`~/Library/Caches/Homebrew/downloads`) and is therefore wiped explicitly, per target formula and its transitive deps, via `brew --cache`. Without this symmetry, local brew numbers come out 5–25× faster than CI's because brew reuses bottles cached by earlier rounds. See [`scripts/bench.sh`](scripts/bench.sh).\n\u003e\n\u003e **Warmup + rotation.** Each package bench opens with a discarded warmup round — every tool runs one install/uninstall pair whose timings are thrown away — so DNS, TLS session cache, TCP congestion window, and disk caches are populated before timing starts. The measured rounds then rotate tool order (round _r_ starts with `tools[r mod N]`), so no single tool reliably eats the \"cold network\" slot or benefits from the warmest one. Without these two protocol fixes, whichever tool went first was systematically slower and whichever went last was systematically faster — independent of the install logic being measured.\n\u003e\n\u003e **Peer-tool freshness.** `scripts/bench.sh` `git fetch`es nanobrew and zerobrew before each build so the comparison is always malt-today vs. each peer's latest commit, not a weeks-old snapshot. Set `BENCH_SKIP_UPDATE=1` to pin whatever is already checked out (offline / reproducibility mode).\n\u003e\n\u003e **Build recipes.** Each tool is built using the same release flags its upstream ships with: malt `ReleaseSafe` (matches [`.goreleaser.yaml`](.goreleaser.yaml)), nanobrew `ReleaseFast`, zerobrew `cargo build --release`. Binary sizes may therefore differ from the numbers shown on each tool's own repo — the gap is almost always version drift (different snapshots over time), not a flag difference.\n\u003e\n\u003e **Reproduce locally.** `./scripts/local-bench.sh` runs the four CI phases (tree, wget, ffmpeg, stress-test) in order against the same env (`BENCH_TRUE_COLD=1 BENCH_FAIL_FAST=1`). Add `--clean` to wipe `/tmp` bench state afterwards. For iterative work on a single package, call `scripts/bench.sh \u003cpkg\u003e` directly — `SKIP_BUILD=1` reuses existing binaries, and `SKIP_OTHERS=1` / `SKIP_BREW=1` skip peer comparisons.\n\u003e\n\u003e **bru omitted.** bru previously appeared in this table but was dropped: upstream pins Zig 0.15.2 and relies on `std.heap.ThreadSafeAllocator`, which was removed in Zig 0.16 (what malt and nanobrew now build on). It will be re-added once upstream catches up.\n\n---\n\n## Contributing\n\nContributions are welcome. Please open an issue to discuss before submitting large changes.\n\n## License\n\nmalt is licensed under the [MIT License](LICENSE).\n\nThird-party components and upstream projects — including Homebrew (BSD-2-Clause) and homebrew-core (BSD-2-Clause) — are acknowledged in the [LICENSE](LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findaco%2Fmalt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Findaco%2Fmalt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findaco%2Fmalt/lists"}