A MoonBit-native take on uv: a small, fast toolchain and package manager for MoonBit, written in MoonBit.
mb manages two things, mirroring how uv python / uv run / uv tool
manage Python:
- MoonBit toolchains — download, install and switch between compiler
releases from
cli.moonbitlang.com(mb moon ...). - Tools — install a prebuilt binary from a module's GitHub release,
falling back to downloading a module from
mooncakes.io and compiling it (
mb tool ...).
Everything installs to ~/.local/bin by default. Only Linux (x86_64,
aarch64) and macOS on Apple Silicon (arm64) are supported: the MoonBit
toolchain no longer ships an Intel macOS target, so there is no
darwin-x86_64 build.
curl -LsSf https://raw.githubusercontent.com/tomasky/mb/main/install.sh | bashOr with npm:
pnpm install -g @tomasko/mbOr with the MoonBit toolchain, which builds from source into ~/.moon/bin:
moon install kupas/mb/cmd/mbThe installer and the npm package download the prebuilt binary from GitHub Releases for the current platform. See Build to compile from source.
moon build --target native --release
# binary: _build/native/release/build/cmd/mb/mb.exemb run [--moon <version>] <command> [args...]
mb moon list | install [version] | uninstall <version> | default <version> | which [version] | latest
mb tool install <author>/<module>[@<version>] [--force] [--bin-dir <dir>] [--moon <version>]
mb tool list [--outdated] | upgrade [--all] <name>... | uninstall [--all] <name>...
mb env set <KEY>=<VALUE> [--global] [--plain] | rm [--global] <KEY>... | list [--global]
mb help | version
Every command and subcommand also understands -h/--help (which wins over
any other argument, as in uv). --bin <dir> is accepted as an alias for
--bin-dir <dir>.
mb moon install # install the latest release
mb moon install 0.10.14 # install the release with moonc 0.10.14
mb moon install nightly # install the nightly build
mb moon list # installed toolchains + the release channel
mb moon default 0.10.14 # select the version ~/.local/bin points at
mb moon which # print the active moon binary
mb moon uninstall 0.10.14 # remove a toolchain and its ~/.local/bin shimsmb moon list lists the installed toolchains, then the versions published on
the release channel (cli.moonbitlang.com/version.json, i.e. what latest
resolves to).
Toolchains live under $MB_HOME/moon/<version> and the moon, moonc,
moonfmt, ... binaries are symlinked into ~/.local/bin. Uninstalling a
toolchain removes those symlinks too (it matches them by target, so nothing is
left dangling), and re-points them at another installed toolchain when one is
available.
Toolchains live under $MB_HOME/moon/<version> and the moon, moonc,
moonfmt, ... binaries are symlinked into ~/.local/bin. Installing a
toolchain also downloads and bundles core so that builds work offline.
mb moon install 0.10.14 resolves a bare compiler version to the full release
key (e.g. 0.10.14+7d59c7ec9). Pass the full X.Y.Z+hash string to pin an
exact build.
mb run moon build # run with the default managed toolchain
mb run --moon 0.10.14 moon test
mb run --moon nightly node script.jsmb run prepends the selected toolchain's bin to PATH and points
MOON_HOME at it before executing the command, and exports the dotenv files
described below. When no managed toolchain is selected it falls back to the
moon already on PATH.
mb env set API_KEY=secret # encrypted, stored in ./.env
mb env set GREETING "hello world" --plain
mb env set --global GITHUB_TOKEN=ghp_xxx
mb env list # clear values on a terminal, masked otherwise
mb env list --mask # always mask encrypted values
mb env list --global
mb env rm API_KEYmb env manages the dotenv files that mb run loads:
- project (default):
./.env, with./.mbenvalso loaded when present; - global (
--global/-g):$HOME/.mbenv, which additionally stores the encryption key.
Values are encrypted by default and written as KEY=ENC[<hex>]; the keys stay
in plaintext, so listing never needs the key. mb env list reveals values only
when stdout is a terminal, so a piped or captured listing shows <encrypted>
instead; --mask forces masking. --plain stores a value verbatim, so an
ordinary hand-written .env keeps working and plain and encrypted values can
be mixed.
This TTY check is best-effort only: mb run must decrypt to hand variables to
the child process, and the key lives in $HOME/.mbenv, so a process with shell
access can still recover values (for example via mb run printenv). Treat the
encryption as protection at rest (git, sync, backups), not as a boundary
against another process running as you.
Encryption is ChaCha20 + HMAC-SHA256 in encrypt-then-MAC order
(moonbitlang/x/crypto ships no AEAD): a random 96-bit nonce per value and two
subkeys derived from the master key with HMAC-SHA256. The tag is verified
before decryption, so a wrong key or a tampered value is rejected.
The first time a value has to be encrypted, mb env set prompts for a key and
stores it in $HOME/.mbenv as a # mb-env-key: <key> directive. Set
MB_ENV_KEY to override it (useful in CI); using the same value on another
machine reads the same files.
mb run loads $HOME/.mbenv, then ./.mbenv, then ./.env (later files win)
and exports the result before running the command. MoonBit programs can do the
same with the single public entry point load_dotenv().
mb tool install bobzhang/zip # a mooncakes.io module, latest version
mb tool install bobzhang/zip@0.1.1 # pinned version
mb tool install denoland/deno # any GitHub repository with releases
mb tool install denoland/deno@2.9.7 # pinned to a release tag
mb tool install https://github.com/BurntSushi/ripgrep
mb tool install author/tool --moon 0.10.14 --bin-dir ~/.local/bin
mb tool install author/tool -i # choose which executables to install
mb tool ls # `ls` aliases `list`
mb tool list --outdated
mb tool upgrade author/tool # one or more names, or --all
mb tool rm author/tool # `rm`/`remove` alias `uninstall`Every subcommand accepts short aliases (install/add/i,
upgrade/update/up, list/ls, uninstall/remove/rm, and
list/ls for mb env and mb moon).
mb tool install resolves the name on mooncakes.io first. When the name is a
registered module it prefers a prebuilt binary from the module's GitHub
release and only compiles from source when there is none:
- fetch
mooncakes.io/api/v0/manifest/<author>/<module>to get the version, checksum andmetadata.repository; - if the repository is on GitHub, look for a release asset that matches the
host OS and CPU architecture. A pinned version is matched against the
v<version>and<version>tags; without a pin the repository's latest release is used. Assets that are archives (zip,tar,tar.gz/tgz,tar.xz/txz,tar.bz2,tar.zst, baregz/xz/bz2/zst, ...) are unpacked and every executable inside is installed; a plain asset is installed under a name derived from its filename (somb-linux-x86_64becomesmb). GitHub'ssha256digest is verified when present; - otherwise fall back to building from source: download
https://download.mooncakes.io/user/<author>/<module>/<version>.zip, verify its SHA-256 against the registry checksum, extract it to$MB_HOME/tool/<author>-<module>/<version>/srcand runmoon build --target native --releasewith a managed (or system) toolchain; - copy every executable into
$MB_HOME/tool/<author>-<module>/<version>/binand create a symlink to it in~/.local/bin(or the--bin-dir <dir>given), the wayuvexposes tool executables.
Executables are never clobbered silently: if a target name already exists in
the bin directory (whether an unrelated file or another tool's symlink),
mb tool install fails with executable already exists: <name> (use --force to overwrite) and exits 2, matching uv. Pass --force to replace it.
With -i/--interactive, a tool that publishes several executables prompts
with a checkbox list instead of installing all of them: space toggles,
up/down (or j/k) move, a toggles everything, enter confirms and
esc cancels the installation. -i needs a real terminal; a non-interactive
invocation reports the candidate names instead of guessing.
Because the bin directory only holds symlinks into $MB_HOME/tool/..., the
link target decides ownership: mb tool uninstall removes a link only when it
still points into that tool version's directory, so a name that was taken over
by another tool is left untouched. Upgrading just repoints the symlink.
A name that is not on mooncakes.io is treated as a GitHub repository —
owner/repo, owner/repo@tag or a full URL — and installed from its releases
the same way. It is recorded under the repository slug and the release tag.
mb tool list prints the tools directory ($MB_HOME/tool), and for each tool
the directory its binaries were installed into and any that are missing. With
--outdated it checks for a newer version: a mooncakes.io module is looked up
in the registry, and a GitHub-only tool (one that is not a registered module)
is checked against its repository's latest release. mb tool uninstall removes
the symlinks from that same directory, so a tool installed with --bin <dir>/
--bin-dir <dir> is cleaned up correctly too.
mb tool upgrade takes one or more names (a version specifier is ignored and
the latest version is installed) or --all for every installed tool; it always
rebuilds each tool into the directory it was originally installed into. As in
uv, an upgrade or uninstall without any name and without --all is a usage
error.
| Variable | Meaning | Default |
|---|---|---|
MB_HOME |
Data directory for toolchains and tools | ~/.local/share/mb |
MB_BIN_DIR |
Where executables are installed | ~/.local/bin |
XDG_DATA_HOME |
Used when MB_HOME is unset |
~/.local/share |
MB_ENV_KEY |
Encryption key for .env values |
~/.mbenv directive |
GITHUB_TOKEN |
GitHub API token for release lookups (also GH_TOKEN) |
unset |
$MB_HOME/
moon/<version>/ # a full MoonBit toolchain (bin, lib, lib/core)
moon/.default # the selected version
tool/<author>-<name>/<version>/
src/ # extracted mooncakes source
bin/ # the installed executables
tool.json # { name, version, bins, bin }
~/.local/bin/<name> # symlink -> $MB_HOME/tool/.../bin/<name>
./.env # project env (values encrypted)
./.mbenv # optional second project env file
~/.mbenv # global env + the `# mb-env-key:` directive
-
.github/workflows/ci.ymlrunsmoon check,moon testand a native release build on Linux and macOS. -
.github/workflows/release.ymlbuildsmbforlinux-x86_64,linux-aarch64anddarwin-aarch64and attaches the binaries (plus.sha256checksums) to a GitHub Release when av*tag is pushed:git tag v0.2.1 && git push origin v0.2.1 -
npmjs/is an npm wrapper.pnpm install -g @tomasko/mbdownloads the matching release asset and exposes thembcommand. It is published separately to npm:cd npmjs && npm publish --access public
-
install.shis thecurl | bashinstaller:curl -LsSf https://raw.githubusercontent.com/tomasky/mb/main/install.sh | bashIt detects the platform, downloads
mb-<target>(plus its.sha256) from the latest release and installs it into~/.local/bin. Options:--version <v>,--install-dir <dir>,--no-verify; environment:MB_VERSION,MB_INSTALL_DIR,MB_REPO,MB_DOWNLOAD_BASE.Publishing to mooncakes.io ignores
npmjs/:.moonignoreat the repository root excludes it (and build/editor junk) from the package thatmoon publishuploads.
moon check # type-check
moon test # run the test suite
moon info && moon fmt