Skip to content

chancancode/miseo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

miseo

Stable global CLI tools on top of mise.

miseo (pronounced "miso") manages each global CLI tool as its own isolated environment, pinned to a specific runtime version, so commands stay reliable as you move between projects and upgrade default runtime versions on your system.

Install

mise use -g cargo:miseo

Add ~/.miseo/.bin to your $PATH.

Usage

miseo install npm:http-server
miseo install npm:http-server --use node@22
miseo upgrade npm:http-server
miseo upgrade npm:http-server --use node@22
miseo uninstall npm:http-server

Why miseo exists

mise is excellent for managing runtimes and toolchains within the context of a project. But sometimes you are just trying to install a global tool.

Consider http-server: a simple CLI that serves the current directory over HTTP. It is useful when prototyping static sites, demos, or quick local previews. You want it available globally so you can run it from anywhere.

http-server happens to be implemented in Node and distributed via npm (npm install -g http-server), but those are implementation details. Your mental model is that you are installing a global CLI tool, you just want it to work and keep working as you switch between projects and upgrade things on your system.

In that mode, you probably don't care which version of Node it uses. The system default at installation time is usually fine. Once installed, you want it to keep working the same way until you have a reason to upgrade. And when you do upgrade, you might choose to repin its runtime to your current default.

This is where you run into friction with mise. When you install a tool globally (mise use -g npm:http-server), the package is installed once, but execution still follows mise's active runtime context. In practice, that means the same global tool can run with different runtimes in different directories. Things may break, native extensions may stop working, you may see unexpected deprecation warnings – not ideal for "install once and keep stable until I explicitly change it" global utilities.

miseo exists to close that gap while staying in the mise ecosystem.

Coming from Volta (R.I.P.), this is a problem it solved nicely, and it is the feature I missed the most when moving to mise. The goal of miseo is to be a lightweight layer on top of mise for a "Volta lite" global tools experience.

Core model

The core idea is simple: every global utility is its own mini mise project.

Instead of npm install -g http-server or mise use -g npm:http-server, you run:

miseo install npm:http-server

Under the hood, miseo creates a dedicated tool root at ~/.miseo/npm-http-server and tracks it in a miseo-owned manifest (~/.miseo/.miseo-installs.toml).

Each installed target goes into a versioned runtime-scoped directory, typically:

  • ~/.miseo/npm-http-server/14.1.1+node-22.13.1

Then miseo links the shim from ~/.miseo/.bin to the currently active install:

  • ~/.miseo/.bin/http-server -> ~/.miseo/npm-http-server/current/.miseo/http-server

All you have to do is to put ~/.miseo/.bin in your PATH and this all works. Because each tool has its own pinned runtime, upgrades in unrelated projects and global default cannot accidentally break your global utility.

Local precedence

Some tools are dual-purpose: they can act as a global utility, but specific projects may also want to install a local version. For example, you might have a global http-server, while one project pins a different version for its demo.

Volta handles this with smart, project-aware shims. The http-server shim in Volta would first check whether you are currently in a Node project with a http-server dependency and, if so, transparently execute the local version instead. That is a great experience, but it depends on deep understanding of ecosystem-specific logic and conventions (package manifests, local install conventions, etc.). Volta is a JavaScript-specific manager; mise/miseo are trying to stay more general across ecosystems, so miseo does not currently implement that style of interception.

By default, miseo behaves like ordinary global installs: no implicit local/global switching logic. In our example, the global http-server shim always executes the globally installed version, just like npm install -g http-server would. Usually, in the Node ecosystem, you solve this by using launchers like npx http-server, pnpm http-server, etc. If that works for you, you can keep doing that; miseo does not get in the way.

The cleaner cross-ecosystem approach is to let PATH ordering handle precedence by configuring mise to add local bin directories when you enter a project. This requires configuring mise PATH behavior for local package bins where relevant. For example, in a Node project:

[env]
_.path = ["{{config_root}}/node_modules/.bin"]

When that is present and mise activate is used, mise adds the local node_modules/.bin into an earlier PATH position, naturally overriding the global shim in ~/.miseo/.bin.

If I find myself really missing the intelligent shims from Volta, maybe I'll revisit this design.

Backend agnostic

While we mostly discussed miseo in the context of NPM packages so far, just like mise and unlike Volta, miseo does not hardcode ecosystem-specific knowledge. It simply delegates to a few key mise commands so the pattern can be generalized to any backend.

That said, the pattern provided by miseo is most useful when used with backends that installs global tools with a "floating" runtime, like NPM.

For backends like Cargo, since you are only compiling from source once at installation time against your then-current Rust toolchain, the runtime behavior is locked in at that point and baked into the compiled binary, so miseo install may not add much value compared to mise use -g, though it also shouldn't harm anything. It does, however, provide a convenient way to use a different version of the tool than your global default version, which can come in handy when running into compatibility issues.

It's worth mentioning that miseo does not conflict with mise use -g, so you can mix usages if the latter works better in certain scenarios, though I'd be curious to hear about the problems you ran into.

Limitations and trade-offs

The explicit trade-off we made with miseo approach is to prioritize stability for global tools and make upgrading, including its runtime environment, an explicit choice.

From an implementation standpoint, this means giving each CLI tool an explicit mise environment which we materialize in the shim before executing the tool. This adds a small amount of overhead but for the most part should be imperceptible, though that depends on the expected performance and use cases of the CLI tool in question. If you noticed substantial degradation, please open an issue with the specifics of your use case so we can discuss potential improvements.

More importantly, from a functional standpoint, this strategy means that if the CLI tool tries to resolve or shell out to the runtime tools that are part of their environment, it will get the versions that are pinned for them. For general purpose CLI utilities like http-server and ripgrep, this is fine and may even be important for correctness (e.g. if they need to spawn sub-processes to parallelize work).

However, for devtools like tsx, this might not be what you want. For those kinds of tools, the floating approach of mise use -g may work better. Arguably, the more correct fix is to add tsx to your project's dev dependencies and let the project-local version take precedence.

About

Stable global CLI tools on top of mise

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages