TL;DR: z myproj to get to your project, mise run dev to run it, starship to see where you are, Ctrl-T to pick a file. Four tools for improving your terminal session.

Each of these four tools is useful on its own when working in the terminal. In the first book of the Modern CLI Stack series, each tool is presented in isolation. What's missing is the session where all four run together.

The goal of this issue is to show that session. Five minutes of a morning, from a terminal you just launched to an open file, with each tool doing its job in sequence.

Step 1: jump to your project directory using zoxide

z myproj

From any directory, z followed by a fragment of the project's name lands you there. zoxide ranks the directories you actually visit, so that after a few days of normal work the fragment you use most often is the fragment that works. No need to cd ~/work/some/path/to/myproj, no tab-completion walk, and no mental effort for the directory paths.

The first days, the ranking database zoxide uses is thin, so the trick is to use z sparingly and continue with cd the normal way; the tool is learning your habits.

Step 2: run the project using mise

Now, you are in the project. Let's say it uses Node and it contains the following mise.toml file:

[tools]
node = "20"

[tasks.dev]
run = "node dev-server.js"

[tasks.build]
run = "npm run build"

The project's tasks are declared next to its tools (here the Node tool is pinned to a version 20). Based on that setup, you can run the dev task using:

mise run dev

The advantage: No need to look into the README for the "how do we start this" question, no trying npm run to then find out that the project is not using npm, no remembering whether this one uses Make, the other one a shell script, etc. You can do things the same way for every project, using:

  • mise tasks ls to see what it can do,

  • mise run foo to run the configured task foo.

Note that the pinned versions for the tools are guaranteed for the task: mise run always resolve the versions from mise.toml, whatever else your shell has on PATH.

Step 3: see where you are via starship

While you work, the prompt shows the context. Inside the project, once it is versioned, the prompt shows the git branch; so you don't need to git status when you arrive in your project. When a toolchain (Node or Python for example) is active, it shows the version, so a wrong compatibility situation may be caught immediately at the prompt, not after running some commands.

Step 4: pick a file using fzf

Do Ctrl-T, type "app" to do a fuzzy search over the project's files, pick the file you need and press Enter. The command line now holds the path you picked, pasted from fzf's fuzzy search. You don't have to remember whether it was src/app.js or app/index.js; you type the fragment and pick from the shortlist.

And the same key works in the middle of your command: type vim , press Ctrl-T, and the picker drops the path in place.

What the composition brings

None of these steps is impressive alone. But the composition removes the four small frictions you may have opening every working session: where is the project (zoxide), how do I run it (mise), what am I on (starship), which file did I mean (fzf). Each answer arrives in less than a second, and none of them occupies your attention.

Working this way assumes the four terminal tools are installed and wired into your shell (using mise activate, starship init, zoxide init, and fzf's keybindings sourced). If you came from The Modern CLI Stack book, you have the instructions about that wiring. If not, first follow the docs to install them and add the four init lines to your ~/.bashrc or ~/.zshrc file.

One overlap to mention: you may already have a version manager for a language mise also handles, and the two can coexist. You can have Node handled via fnm or nvm, Python handled via uv, and use mise to run the rest of your stack (via mise run). What you give up, in that case, is mise managing that language's interactive versions; what you keep is one task runner for every project. The split works, but you need to know exactly which manager owns which language on your machine.

Also note that, as I currently do for my daily work, you may choose to use uv (and no mise) for all Python stuff, and then use mise for other projects when needed.

Try it

z <your most-visited project>
mise tasks ls

If the project has no mise.toml, add the two tasks you actually run:

mise use node@20          # pin the toolchain your project needs
mise tasks add dev -- <your dev command>      # the command you type to start work
mise tasks add build -- <your build command>  # the command you type to build the project

<your dev command> and <your build command> are placeholders — substitute the real commands you run today.

Next time you start the session for the project, use this and feel the improvement.

One CLI trick

mise exec node@20 -- node --version
# or
mise x node@20 -- node --version

mise x (short for mise exec) runs one command with the pinned tool, without a mise.toml file: use it to try a version or run a script against a specific node. It's the one-shot form of the guarantee mise run gives your tasks, available anywhere.

If you want the full toolkit, The Modern CLI Stack — 13 Tools Brief is a free ~50-page PDF + EPUB covering mise, starship, zoxide, fzf, broot, ripgrep, fd, bat, eza, delta, tldr, atuin, lazygit.

Reply

Avatar

or to participate