I use just on basically every project I touch. It's good. But there were a few things about it that annoyed me often enough that I ended up writing my own runner instead of just living with it, which is how you end up with yet another CLI tool nobody asked for. Anyway, here it is: ndo.
Three things specifically:
Every time I wanted a one-line shortcut added, I had to go open the justfile, find where to put it, type it out, save, switch back. For a two-second alias that's way more steps than it should be.
So ndo add is a real command:
ndo add deploy "./scripts/deploy.sh {{env}}" --param env --local --desc "Deploy to the given environment"
No editor round trip. Leave off --local and it goes into a central file instead (~/.ndo/central.toml) that's shared across every project on the machine, so stuff like open or a couple git shortcuts don't have to be re-declared per repo. just doesn't really give you a clean way to split "recipes I want everywhere" from "recipes for this one project" - it's one file. ndo just has both, and if a recipe exists in both, local wins, no warning printed. That's on purpose, not an oversight - I don't want a tool nagging me every single run about something that's expected 99% of the time. It's written down in the docs once instead.
The other thing: passing arguments. I wanted ndo open ./file.txt to just bind ./file.txt to the recipe's parameter without extra syntax at the call site. That part works now, which honestly is most of why I use this over just day to day.
There's also a lookup-table thing (ndo var) that I added mostly for myself, for aliasing folder paths I open constantly:
ndo add o "code {{folder}}" --param folder --local
ndo var add folder work "C:\Users\dev\projects" --local
ndo o work # -> code C:\Users\dev\projects
ndo o D:\some\other\path # no match, so it's used as-is
and recipe dependencies, if you need one recipe to run others first:
ndo add deploy "./scripts/deploy.sh {{env}}" --param env --depends build --depends lint --local
build and lint run first in order, and if two recipes both depend on the same thing it only runs once across the whole chain.
Quickstart if you want to poke at it:
ndo init
ndo add open "code {{file}}" --param file --local
ndo open ./main.go
ndo list
Single static binary, Linux/macOS/Windows, amd64/arm64. TOML config, no YAML anywhere (recipes are shell strings full of : and {{}} — YAML's implicit typing makes that miserable, TOML doesn't have that problem).
brew install green-threads/ndo/ndo
# or on Windows
scoop bucket add ndo https://github.com/green-threads/scoop-ndo
scoop install ndo
# or
curl -fsSL https://raw.githubusercontent.com/green-threads/ndo/main/install/install.sh | sh
v1.1.0 is current, just added colored output for list/var list/errors. It's still small on purpose — no --parallel, no remote imports, nothing fancy yet. I'd rather the core three things above stay solid than bolt on features nobody's asked for.
Repo: https://github.com/green-threads/ndo
Docs: https://ndo.greenthreads.dev
If anyone's hit the same friction with just (or has reasons the silent override thing is a bad idea) I'd genuinely like to hear it in the comments.
Top comments (0)